This file is a merged representation of the entire codebase, combined into a single document by Repomix.
The content has been processed where empty lines have been removed, content has been formatted for parsing in plain style, content has been compressed (code blocks are separated by ⋮---- delimiter), security check has been disabled.

================================================================
File Summary
================================================================

Purpose:
--------
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.

File Format:
------------
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files (if enabled)
5. Multiple file entries, each consisting of:
  a. A separator line (================)
  b. The file path (File: path/to/file)
  c. Another separator line
  d. The full contents of the file
  e. A blank line

Usage Guidelines:
-----------------
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.

Notes:
------
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Empty lines have been removed from all files
- Content has been formatted for parsing in plain style
- Content has been compressed - code blocks are separated by ⋮---- delimiter
- Security check has been disabled - content may contain sensitive information
- Files are sorted by Git change count (files with more changes are at the bottom)


================================================================
Directory Structure
================================================================
.cursor/
  rules/
    core-mcp-objects.mdc
.github/
  ISSUE_TEMPLATE/
    bug.yml
    config.yml
    enhancement.yml
  workflows/
    labeler.yml
    publish.yml
    run-static.yml
    run-tests.yml
  dependabot.yml
  labeler.yml
  release.yml
docs/
  .cursor/
    rules/
      mintlify.mdc
  clients/
    auth/
      bearer.mdx
      oauth.mdx
    client.mdx
    elicitation.mdx
    logging.mdx
    messages.mdx
    progress.mdx
    prompts.mdx
    resources.mdx
    roots.mdx
    sampling.mdx
    tools.mdx
    transports.mdx
  community/
    README.md
    showcase.mdx
  css/
    banner.css
    python-sdk.css
    style.css
    version-badge.css
  deployment/
    asgi.mdx
    running-server.mdx
  getting-started/
    installation.mdx
    quickstart.mdx
    welcome.mdx
  integrations/
    anthropic.mdx
    chatgpt.mdx
    claude-code.mdx
    claude-desktop.mdx
    contrib.mdx
    eunomia-authorization.mdx
    gemini.mdx
    openai.mdx
  patterns/
    cli.mdx
    decorating-methods.mdx
    http-requests.mdx
    testing.mdx
    tool-transformation.mdx
  python-sdk/
    fastmcp-cli-__init__.mdx
    fastmcp-cli-claude.mdx
    fastmcp-cli-cli.mdx
    fastmcp-cli-run.mdx
    fastmcp-client-__init__.mdx
    fastmcp-client-auth-__init__.mdx
    fastmcp-client-auth-bearer.mdx
    fastmcp-client-auth-oauth.mdx
    fastmcp-client-client.mdx
    fastmcp-client-logging.mdx
    fastmcp-client-oauth_callback.mdx
    fastmcp-client-progress.mdx
    fastmcp-client-roots.mdx
    fastmcp-client-sampling.mdx
    fastmcp-client-transports.mdx
    fastmcp-exceptions.mdx
    fastmcp-prompts-__init__.mdx
    fastmcp-prompts-prompt_manager.mdx
    fastmcp-prompts-prompt.mdx
    fastmcp-resources-__init__.mdx
    fastmcp-resources-resource_manager.mdx
    fastmcp-resources-resource.mdx
    fastmcp-resources-template.mdx
    fastmcp-resources-types.mdx
    fastmcp-server-__init__.mdx
    fastmcp-server-auth-__init__.mdx
    fastmcp-server-auth-auth.mdx
    fastmcp-server-auth-providers-__init__.mdx
    fastmcp-server-auth-providers-bearer_env.mdx
    fastmcp-server-auth-providers-bearer.mdx
    fastmcp-server-auth-providers-in_memory.mdx
    fastmcp-server-context.mdx
    fastmcp-server-dependencies.mdx
    fastmcp-server-http.mdx
    fastmcp-server-middleware-__init__.mdx
    fastmcp-server-middleware-error_handling.mdx
    fastmcp-server-middleware-logging.mdx
    fastmcp-server-middleware-middleware.mdx
    fastmcp-server-middleware-rate_limiting.mdx
    fastmcp-server-middleware-timing.mdx
    fastmcp-server-middleware.mdx
    fastmcp-server-openapi.mdx
    fastmcp-server-proxy.mdx
    fastmcp-server-server.mdx
    fastmcp-settings.mdx
    fastmcp-tools-__init__.mdx
    fastmcp-tools-tool_manager.mdx
    fastmcp-tools-tool_transform.mdx
    fastmcp-tools-tool.mdx
    fastmcp-utilities-__init__.mdx
    fastmcp-utilities-cache.mdx
    fastmcp-utilities-components.mdx
    fastmcp-utilities-exceptions.mdx
    fastmcp-utilities-http.mdx
    fastmcp-utilities-inspect.mdx
    fastmcp-utilities-json_schema.mdx
    fastmcp-utilities-logging.mdx
    fastmcp-utilities-mcp_config.mdx
    fastmcp-utilities-openapi.mdx
    fastmcp-utilities-tests.mdx
    fastmcp-utilities-types.mdx
  servers/
    auth/
      bearer.mdx
    composition.mdx
    context.mdx
    elicitation.mdx
    logging.mdx
    middleware.mdx
    openapi.mdx
    progress.mdx
    prompts.mdx
    proxy.mdx
    resources.mdx
    sampling.mdx
    server.mdx
    tools.mdx
  snippets/
    version-badge.mdx
    youtube-embed.mdx
  tutorials/
    create-mcp-server.mdx
    mcp.mdx
    rest-api.mdx
  changelog.mdx
  docs.json
  updates.mdx
examples/
  atproto_mcp/
    src/
      atproto_mcp/
        _atproto/
          __init__.py
          _client.py
          _posts.py
          _profile.py
          _read.py
          _social.py
        __init__.py
        __main__.py
        server.py
        settings.py
        types.py
    demo.py
    pyproject.toml
    README.md
  smart_home/
    src/
      smart_home/
        lights/
          hue_utils.py
          server.py
        __init__.py
        __main__.py
        hub.py
        settings.py
    pyproject.toml
    README.md
  complex_inputs.py
  config_server.py
  desktop.py
  echo.py
  get_file.py
  in_memory_proxy_example.py
  memory.py
  mount_example.py
  sampling.py
  screenshot.py
  serializer.py
  simple_echo.py
  tags_example.py
  text_me.py
src/
  fastmcp/
    cli/
      __init__.py
      claude.py
      cli.py
      run.py
    client/
      auth/
        __init__.py
        bearer.py
        oauth.py
      __init__.py
      client.py
      elicitation.py
      logging.py
      messages.py
      oauth_callback.py
      progress.py
      roots.py
      sampling.py
      transports.py
    contrib/
      bulk_tool_caller/
        __init__.py
        bulk_tool_caller.py
        example.py
        README.md
      component_manager/
        __init__.py
        component_manager.py
        component_service.py
        example.py
        README.md
      mcp_mixin/
        __init__.py
        example.py
        mcp_mixin.py
        README.md
      README.md
    prompts/
      __init__.py
      prompt_manager.py
      prompt.py
    resources/
      __init__.py
      resource_manager.py
      resource.py
      template.py
      types.py
    server/
      auth/
        providers/
          bearer_env.py
          bearer.py
          in_memory.py
        __init__.py
        auth.py
      middleware/
        __init__.py
        error_handling.py
        logging.py
        middleware.py
        rate_limiting.py
        timing.py
      __init__.py
      context.py
      dependencies.py
      elicitation.py
      http.py
      low_level.py
      openapi.py
      proxy.py
      server.py
    tools/
      __init__.py
      tool_manager.py
      tool_transform.py
      tool.py
    utilities/
      __init__.py
      cache.py
      components.py
      exceptions.py
      http.py
      inspect.py
      json_schema_type.py
      json_schema.py
      logging.py
      mcp_config.py
      openapi.py
      tests.py
      types.py
    __init__.py
    exceptions.py
    settings.py
tests/
  auth/
    providers/
      test_bearer_env.py
      test_bearer.py
      test_token_verifier.py
    test_oauth_client.py
  cli/
    test_cli.py
    test_run.py
  client/
    test_client.py
    test_elicitation.py
    test_logs.py
    test_notifications.py
    test_openapi.py
    test_progress.py
    test_roots.py
    test_sampling.py
    test_sse.py
    test_stdio.py
    test_streamable_http.py
  contrib/
    __init__.py
    test_bulk_tool_caller.py
    test_component_manager.py
    test_mcp_mixin.py
  deprecated/
    __init__.py
    test_deprecated.py
    test_mount_import_arg_order.py
    test_mount_separators.py
    test_resource_prefixes.py
    test_route_type_ignore.py
    test_settings.py
  prompts/
    test_prompt_manager.py
    test_prompt.py
  resources/
    test_file_resources.py
    test_function_resources.py
    test_resource_manager.py
    test_resource_template.py
    test_resources.py
  server/
    http/
      test_auth_setup.py
      test_bearer_auth_backend.py
      test_custom_routes.py
      test_http_dependencies.py
      test_http_middleware.py
    middleware/
      test_error_handling.py
      test_logging.py
      test_middleware.py
      test_rate_limiting.py
      test_timing.py
    openapi/
      test_openapi_path_parameters.py
      test_openapi.py
      test_route_map_fn.py
    test_app_state.py
    test_auth_integration.py
    test_context.py
    test_file_server.py
    test_import_server.py
    test_logging.py
    test_mount.py
    test_proxy.py
    test_resource_prefix_formats.py
    test_run_server.py
    test_server_interactions.py
    test_server.py
    test_tool_annotations.py
    test_tool_exclude_args.py
  test_servers/
    fastmcp_server.py
    sse.py
    stdio.py
  tools/
    test_tool_manager.py
    test_tool_transform.py
    test_tool.py
  utilities/
    openapi/
      __init__.py
      conftest.py
      test_openapi_advanced.py
      test_openapi_fastapi.py
      test_openapi.py
    __init__.py
    test_cache.py
    test_inspect.py
    test_json_schema_type.py
    test_json_schema.py
    test_logging.py
    test_mcp_config.py
    test_tests.py
    test_typeadapter.py
    test_types.py
  test_examples.py
.gitignore
.pre-commit-config.yaml
AGENTS.md
CLAUDE.md
justfile
LICENSE
pyproject.toml
README.md
Windows_Notes.md

================================================================
Files
================================================================

================
File: .cursor/rules/core-mcp-objects.mdc
================
---
description: 
globs: 
alwaysApply: true
---
There are four major MCP object types:

- Tools (src/tools/)
- Resources (src/resources/)
- Resource Templates (src/resources/)
- Prompts (src/prompts)

While these have slightly different semantics and implementations, in general changes that affect interactions with any one (like adding tags, importing, etc.) will need to be adopted, applied, and tested on all others. Be sure to look at not only the object definition but also the related `Manager` (e.g. `ToolManager`, `ResourceManager`, and `PromptManager`). Also note that while resources and resource templates are different objects, they both are handled by the `ResourceManager`.

================
File: .github/ISSUE_TEMPLATE/bug.yml
================
name: 🐛 Bug Report
description: Report a bug or unexpected behavior in FastMCP
labels: [bug, pending]
body:
  - type: markdown
    attributes:
      value: Thank you for contributing to FastMCP! 🙏
  - type: textarea
    id: description
    attributes:
      label: Description
      description: |
        Please explain what you're experiencing and what you would expect to happen instead.
        Provide as much detail as possible to help us understand and solve your problem quickly.
    validations:
      required: true
  - type: textarea
    id: example
    attributes:
      label: Example Code
      description: >
        If applicable, please provide a self-contained,
        [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)
        demonstrating the bug.
        If possible, your example should be a single-file script. Instead of `.run()`-ing an MCP server, use a `Client` to directly interact with it (`async with Client(mcp) as client: ...`) and demonstrate the issue.
      placeholder: |
        from fastmcp import FastMCP, Client
        mcp = FastMCP()
        async with Client(mcp) as client:
            ...
      render: Python
  - type: textarea
    id: version
    attributes:
      label: Version Information
      description: |
        Please provide information about your FastMCP version, MCP version, Python version, and OS.
        To get this information, run the following command in your terminal and paste the output below:
        ```bash
        fastmcp version
        ```
        If there is other information that would be helpful, please include it as well.
      render: Text
    validations:
      required: true
  - type: textarea
    id: additional_context
    attributes:
      label: Additional Context
      description: |
        Add any other context about the problem here. This could include:
        - The full error message and traceback (if applicable)
        - Information about your environment (e.g., virtual environment, installed packages)
        - Steps to reproduce the issue
        - Any recent changes in your code or setup that might be relevant

================
File: .github/ISSUE_TEMPLATE/config.yml
================
blank_issues_enabled: false
contact_links:
  - name: FastMCP Documentation
    url: https://gofastmcp.com
    about: Please review the documentation before opening an issue.
  - name: MCP Python SDK
    url: https://github.com/modelcontextprotocol/python-sdk/issues
    about: Issues related to the low-level MCP Python SDK, including the FastMCP 1.0 module that is included in the `mcp` package, should be filed on the official MCP repository.

================
File: .github/ISSUE_TEMPLATE/enhancement.yml
================
name: 💡 Enhancement Request
description: Suggest an idea or improvement for FastMCP
labels: [enhancement, pending]
body:
  - type: markdown
    attributes:
      value: Thank you for contributing to FastMCP! We value your ideas for improving the framework. 💡
  - type: textarea
    id: description
    attributes:
      label: Enhancement Description
      description: |
        Please describe the enhancement you'd like to see in FastMCP.
        - What problem would this solve?
        - How would this improve your workflow or experience with FastMCP?
        - Are there any alternative solutions you've considered?
    validations:
      required: true
  - type: textarea
    id: use_case
    attributes:
      label: Use Case
      description: |
        Describe a specific use case or scenario where this enhancement would be beneficial.
        If possible, provide an example of how you envision using this feature.
  - type: textarea
    id: example
    attributes:
      label: Proposed Implementation
      description: >
        If you have ideas about how this enhancement could be implemented,
        please share them here. Code snippets, pseudocode, or general approaches are welcome.
      render: Python

================
File: .github/workflows/labeler.yml
================
name: "Pull Request Labeler"
on:
  - pull_request_target
jobs:
  labeler:
    permissions:
      contents: read
      pull-requests: write
      issues: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v5

================
File: .github/workflows/publish.yml
================
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@v4
        with:
          fetch-depth: 0
      - name: "Install uv"
        uses: astral-sh/setup-uv@v6
      - name: Build
        run: uv build
      - name: Publish to PyPi
        run: uv publish -v dist/*

================
File: .github/workflows/run-static.yml
================
name: Run static analysis
env:
  # enable colored output
  # https://github.com/pytest-dev/pytest/issues/7443
  PY_COLORS: 1
on:
  push:
    branches: ["main"]
    paths:
      - "src/**"
      - "tests/**"
      - "uv.lock"
      - "pyproject.toml"
      - ".github/workflows/**"
  # run on all pull requests because these checks are required and will block merges otherwise
  pull_request:
  workflow_dispatch:
permissions:
  contents: read
jobs:
  static_analysis:
    timeout-minutes: 2
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install dependencies
        run: uv sync --dev
      - name: Run pre-commit
        uses: pre-commit/action@v3.0.1

================
File: .github/workflows/run-tests.yml
================
name: Run tests
env:
  # enable colored output
  PY_COLORS: 1
on:
  push:
    branches: ["main"]
    paths:
      - "src/**"
      - "tests/**"
      - "uv.lock"
      - "pyproject.toml"
      - ".github/workflows/**"
  # run on all pull requests because these checks are required and will block merges otherwise
  pull_request:
  workflow_dispatch:
permissions:
  contents: read
jobs:
  run_tests:
    name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        python-version: ["3.10"]
      fail-fast: false
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
      - name: Install FastMCP
        run: uv sync --locked
      - name: Run tests
        run: uv run pytest tests

================
File: .github/dependabot.yml
================
version: 2
updates:
  - package-ecosystem: "uv"
    directory: "/"
    schedule:
      interval: "daily"
    labels:
      - "dependencies"
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"
    labels:
      - "dependencies"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"

================
File: .github/labeler.yml
================
documentation:
  - changed-files:
      - any-glob-to-any-file: "docs/**"
example:
  - changed-files:
      - any-glob-to-any-file:
          - "examples/**"
tests:
  - changed-files:
      - any-glob-to-any-file: "tests/**"
"component: HTTP":
  - changed-files:
      - any-glob-to-any-file: "src/fastmcp/server/http.py"
"component: client":
  - changed-files:
      - any-glob-to-any-file: "src/fastmcp/client/**"
"contrib":
  - changed-files:
      - any-glob-to-any-file: "src/fastmcp/contrib/**"
"component: openapi":
  - changed-files:
      - any-glob-to-any-file:
          - "src/**/*openapi*.py"

================
File: .github/release.yml
================
changelog:
  exclude:
    labels:
      - ignore in release notes
  categories:
    - title: New Features 🎉
      labels:
        - feature
      exclude:
        labels:
          - contrib
    - title: Enhancements 🔧
      labels:
        - enhancement
      exclude:
        labels:
          - breaking change
          - contrib
    - title: Fixes 🐞
      labels:
        - bug
      exclude:
        labels:
          - contrib
    - title: Breaking Changes 🛫
      labels:
        - breaking change
      exclude:
        labels:
          - contrib
    - title: Docs 📚
      labels:
        - documentation
    - title: Examples & Contrib 💡
      labels:
        - example
        - contrib
    - title: Dependencies 📦
      labels:
        - dependencies
    - title: Other Changes 🦾
      labels:
        - "*"

================
File: docs/.cursor/rules/mintlify.mdc
================
---
description: 
globs: *.mdx
alwaysApply: false
---
# Mintlify technical writing assistant

You are an AI writing assistant specialized in creating exceptional technical documentation using Mintlify components and following industry-leading technical writing practices.

## Core writing principles

### Language and style requirements
- Use clear, direct language appropriate for technical audiences
- Write in second person ("you") for instructions and procedures
- Use active voice over passive voice
- Employ present tense for current states, future tense for outcomes
- Maintain consistent terminology throughout all documentation
- Keep sentences concise while providing necessary context
- Use parallel structure in lists, headings, and procedures

### Content organization standards
- Lead with the most important information (inverted pyramid structure)
- Use progressive disclosure: basic concepts before advanced ones
- Break complex procedures into numbered steps
- Include prerequisites and context before instructions
- Provide expected outcomes for each major step
- End sections with next steps or related information
- Use descriptive, keyword-rich headings for navigation and SEO

### User-centered approach
- Focus on user goals and outcomes rather than system features
- Anticipate common questions and address them proactively
- Include troubleshooting for likely failure points
- Provide multiple pathways when appropriate (beginner vs advanced), but offer an opinionated path for people to follow to avoid overwhelming with options

## Mintlify component reference

### Callout components

#### Note - Additional helpful information

<Note>
Supplementary information that supports the main content without interrupting flow
</Note>

#### Tip - Best practices and pro tips

<Tip>
Expert advice, shortcuts, or best practices that enhance user success
</Tip>

#### Warning - Important cautions

<Warning>
Critical information about potential issues, breaking changes, or destructive actions
</Warning>

#### Info - Neutral contextual information

<Info>
Background information, context, or neutral announcements
</Info>

#### Check - Success confirmations

<Check>
Positive confirmations, successful completions, or achievement indicators
</Check>

### Code components

#### Single code block

```javascript config.js
const apiConfig = {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
    'Authorization': `Bearer ${process.env.API_TOKEN}`
}
};
```

#### Code group with multiple languages

<CodeGroup>
```javascript Node.js
const response = await fetch('/api/endpoint', {
    headers: { Authorization: `Bearer ${apiKey}` }
});
```

```python Python
import requests
response = requests.get('/api/endpoint', 
    headers={'Authorization': f'Bearer {api_key}'})
```

```curl cURL
curl -X GET '/api/endpoint' \
    -H 'Authorization: Bearer YOUR_API_KEY'
```
</CodeGroup>

#### Request/Response examples

<RequestExample>
```bash cURL
curl -X POST 'https://api.example.com/users' \
    -H 'Content-Type: application/json' \
    -d '{"name": "John Doe", "email": "john@example.com"}'
```
</RequestExample>

<ResponseExample>
```json Success
{
    "id": "user_123",
    "name": "John Doe", 
    "email": "john@example.com",
    "created_at": "2024-01-15T10:30:00Z"
}
```
</ResponseExample>

### Structural components

#### Steps for procedures

<Steps>
<Step title="Install dependencies">
    Run `npm install` to install required packages.
    
    <Check>
    Verify installation by running `npm list`.
    </Check>
</Step>

<Step title="Configure environment">
    Create a `.env` file with your API credentials.
    
    ```bash
    API_KEY=your_api_key_here
    ```
    
    <Warning>
    Never commit API keys to version control.
    </Warning>
</Step>
</Steps>

#### Tabs for alternative content

<Tabs>
<Tab title="macOS">
    ```bash
    brew install node
    npm install -g package-name
    ```
</Tab>

<Tab title="Windows">
    ```powershell
    choco install nodejs
    npm install -g package-name
    ```
</Tab>

<Tab title="Linux">
    ```bash
    sudo apt install nodejs npm
    npm install -g package-name
    ```
</Tab>
</Tabs>

#### Accordions for collapsible content

<AccordionGroup>
<Accordion title="Troubleshooting connection issues">
    - **Firewall blocking**: Ensure ports 80 and 443 are open
    - **Proxy configuration**: Set HTTP_PROXY environment variable
    - **DNS resolution**: Try using 8.8.8.8 as DNS server
</Accordion>

<Accordion title="Advanced configuration">
    ```javascript
    const config = {
    performance: { cache: true, timeout: 30000 },
    security: { encryption: 'AES-256' }
    };
    ```
</Accordion>
</AccordionGroup>

### API documentation components

#### Parameter fields

<ParamField path="user_id" type="string" required>
Unique identifier for the user. Must be a valid UUID v4 format.
</ParamField>

<ParamField body="email" type="string" required>
User's email address. Must be valid and unique within the system.
</ParamField>

<ParamField query="limit" type="integer" default="10">
Maximum number of results to return. Range: 1-100.
</ParamField>

<ParamField header="Authorization" type="string" required>
Bearer token for API authentication. Format: `Bearer YOUR_API_KEY`
</ParamField>

#### Response fields

<ResponseField name="user_id" type="string" required>
Unique identifier assigned to the newly created user.
</ResponseField>

<ResponseField name="created_at" type="timestamp">
ISO 8601 formatted timestamp of when the user was created.
</ResponseField>

<ResponseField name="permissions" type="array">
List of permission strings assigned to this user.
</ResponseField>

#### Expandable nested fields

<ResponseField name="user" type="object">
Complete user object with all associated data.

<Expandable title="User properties">
    <ResponseField name="profile" type="object">
    User profile information including personal details.
    
    <Expandable title="Profile details">
        <ResponseField name="first_name" type="string">
        User's first name as entered during registration.
        </ResponseField>
        
        <ResponseField name="avatar_url" type="string | null">
        URL to user's profile picture. Returns null if no avatar is set.
        </ResponseField>
    </Expandable>
    </ResponseField>
</Expandable>
</ResponseField>

### Interactive components

#### Cards for navigation

<Card title="Getting started guide" icon="rocket" href="/quickstart">
Complete walkthrough from installation to your first API call in under 10 minutes.
</Card>

<CardGroup cols={2}>
<Card title="Authentication" icon="key" href="/auth">
    Learn how to authenticate requests using API keys or JWT tokens.
</Card>

<Card title="Rate limiting" icon="clock" href="/rate-limits">
    Understand rate limits and best practices for high-volume usage.
</Card>
</CardGroup>

### Media and advanced components

#### Frames for images

Wrap all images in frames.

<Frame>
<img src="/images/dashboard.png" alt="Main dashboard showing analytics overview" />
</Frame>

<Frame caption="The analytics dashboard provides real-time insights">
<img src="/images/analytics.png" alt="Analytics dashboard with charts" />
</Frame>

#### Tooltips and updates

<Tooltip tip="Application Programming Interface - protocols for building software">
API
</Tooltip>

<Update label="Version 2.1.0" description="Released March 15, 2024">
## New features
- Added bulk user import functionality
- Improved error messages with actionable suggestions

## Bug fixes
- Fixed pagination issue with large datasets
- Resolved authentication timeout problems
</Update>

## Required page structure

Every documentation page must begin with YAML frontmatter:

```yaml
---
title: "Clear, specific, keyword-rich title"
description: "Concise description explaining page purpose and value"
---
```

## Content quality standards

### Code examples requirements
- Always include complete, runnable examples that users can copy and execute
- Show proper error handling and edge case management
- Use realistic data instead of placeholder values
- Include expected outputs and results for verification
- Test all code examples thoroughly before publishing
- Specify language and include filename when relevant
- Add explanatory comments for complex logic

### API documentation requirements
- Document all parameters including optional ones with clear descriptions
- Show both success and error response examples with realistic data
- Include rate limiting information with specific limits
- Provide authentication examples showing proper format
- Explain all HTTP status codes and error handling
- Cover complete request/response cycles

### Accessibility requirements
- Include descriptive alt text for all images and diagrams
- Use specific, actionable link text instead of "click here"
- Ensure proper heading hierarchy starting with H2
- Provide keyboard navigation considerations
- Use sufficient color contrast in examples and visuals
- Structure content for easy scanning with headers and lists

## AI assistant instructions

### Component selection logic
- Use **Steps** for procedures, tutorials, setup guides, and sequential instructions
- Use **Tabs** for platform-specific content or alternative approaches
- Use **CodeGroup** when showing the same concept in multiple languages
- Use **Accordions** for supplementary information that might interrupt flow
- Use **Cards and CardGroup** for navigation, feature overviews, and related resources
- Use **RequestExample/ResponseExample** specifically for API endpoint documentation
- Use **ParamField** for API parameters, **ResponseField** for API responses
- Use **Expandable** for nested object properties or hierarchical information

### Quality assurance checklist
- Verify all code examples are syntactically correct and executable
- Test all links to ensure they are functional and lead to relevant content
- Validate Mintlify component syntax with all required properties
- Confirm proper heading hierarchy with H2 for main sections, H3 for subsections
- Ensure content flows logically from basic concepts to advanced topics
- Check for consistency in terminology, formatting, and component usage

### Error prevention strategies
- Always include realistic error handling in code examples
- Provide dedicated troubleshooting sections for complex procedures
- Explain prerequisites clearly before beginning instructions
- Include verification and testing steps with expected outcomes
- Add appropriate warnings for destructive or security-sensitive actions
- Validate all technical information through testing before publication

================
File: docs/clients/auth/bearer.mdx
================
---
title: Bearer Token Authentication
sidebarTitle: Bearer Auth
description: Authenticate your FastMCP client with a Bearer token.
icon: key
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.6.0" />

<Tip>
Bearer Token authentication is only relevant for HTTP-based transports.
</Tip>

You can configure your FastMCP client to use **bearer authentication** by supplying a valid access token. This is most appropriate for service accounts, long-lived API keys, CI/CD, applications where authentication is managed separately, or other non-interactive authentication methods.

A Bearer token is a JSON Web Token (JWT) that is used to authenticate a request. It is most commonly used in the `Authorization` header of an HTTP request, using the `Bearer` scheme:

```http
Authorization: Bearer <token>
```


## Client Usage

The most straightforward way to use a pre-existing Bearer token is to provide it as a string to the `auth` parameter of the `fastmcp.Client` or transport instance. FastMCP will automatically format it correctly for the `Authorization` header and bearer scheme.

<Tip>
If you're using a string token, do not include the `Bearer` prefix. FastMCP will add it for you.
</Tip>

```python {5}
from fastmcp import Client

async with Client(
    "https://fastmcp.cloud/mcp", 
    auth="<your-token>",
) as client:
    await client.ping()
```

You can also supply a Bearer token to a transport instance, such as `StreamableHttpTransport` or `SSETransport`:

```python {6}
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

transport = StreamableHttpTransport(
    "http://fastmcp.cloud/mcp", 
    auth="<your-token>",
)

async with Client(transport) as client:
    await client.ping()
```

## `BearerAuth` Helper

If you prefer to be more explicit and not rely on FastMCP to transform your string token, you can use the `BearerAuth` class yourself, which implements the `httpx.Auth` interface.

```python {6}
from fastmcp import Client
from fastmcp.client.auth import BearerAuth

async with Client(
    "https://fastmcp.cloud/mcp", 
    auth=BearerAuth(token="<your-token>"),
) as client:
    await client.ping()
```

## Custom Headers

If the MCP server expects a custom header or token scheme, you can manually set the client's `headers` instead of using the `auth` parameter by setting them on your transport:

```python {5}
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

async with Client(
    transport=StreamableHttpTransport(
        "https://fastmcp.cloud/mcp", 
        headers={"X-API-Key": "<your-token>"},
    ),
) as client:
    await client.ping()
```

================
File: docs/clients/auth/oauth.mdx
================
---
title: OAuth Authentication
sidebarTitle: OAuth
description: Authenticate your FastMCP client via OAuth 2.1.
icon: window
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.6.0" />

<Tip>
OAuth authentication is only relevant for HTTP-based transports and requires user interaction via a web browser.
</Tip>

When your FastMCP client needs to access an MCP server protected by OAuth 2.1, and the process requires user interaction (like logging in and granting consent), you should use the Authorization Code Flow. FastMCP provides the `fastmcp.client.auth.OAuth` helper to simplify this entire process.

This flow is common for user-facing applications where the application acts on behalf of the user.

## Client Usage


### Default Configuration

The simplest way to use OAuth is to pass the string `"oauth"` to the `auth` parameter of the `Client` or transport instance. FastMCP will automatically configure the client to use OAuth with default settings:

```python {4}
from fastmcp import Client

# Uses default OAuth settings
async with Client("https://fastmcp.cloud/mcp", auth="oauth") as client:
    await client.ping()
```


### `OAuth` Helper

To fully configure the OAuth flow, use the `OAuth` helper and pass it to the `auth` parameter of the `Client` or transport instance. `OAuth` manages the complexities of the OAuth 2.1 Authorization Code Grant with PKCE (Proof Key for Code Exchange) for enhanced security, and implements the full `httpx.Auth` interface.

```python {2, 4, 6}
from fastmcp import Client
from fastmcp.client.auth import OAuth

oauth = OAuth(mcp_url="https://fastmcp.cloud/mcp")

async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client:
    await client.ping()
```

#### `OAuth` Parameters

- **`mcp_url`** (`str`): The full URL of the target MCP server endpoint. Used to discover OAuth server metadata
- **`scopes`** (`str | list[str]`, optional): OAuth scopes to request. Can be space-separated string or list of strings
- **`client_name`** (`str`, optional): Client name for dynamic registration. Defaults to `"FastMCP Client"`
- **`token_storage_cache_dir`** (`Path`, optional): Token cache directory. Defaults to `~/.fastmcp/oauth-mcp-client-cache/`
- **`additional_client_metadata`** (`dict[str, Any]`, optional): Extra metadata for client registration


## OAuth Flow

The OAuth flow is triggered when you use a FastMCP `Client` configured to use OAuth.

<Steps>
<Step title="Token Check">
The client first checks the `token_storage_cache_dir` for existing, valid tokens for the target server. If one is found, it will be used to authenticate the client.
</Step>
<Step title="OAuth Server Discovery">
If no valid tokens exist, the client attempts to discover the OAuth server's endpoints using a well-known URI (e.g., `/.well-known/oauth-authorization-server`) based on the `mcp_url`.
</Step>
<Step title="Dynamic Client Registration">
If the OAuth server supports it and the client isn't already registered (or credentials aren't cached), the client performs dynamic client registration according to RFC 7591.
</Step>
<Step title="Local Callback Server">
A temporary local HTTP server is started on an available port. This server's address (e.g., `http://127.0.0.1:<port>/callback`) acts as the `redirect_uri` for the OAuth flow.
</Step>
<Step title="Browser Interaction">
The user's default web browser is automatically opened, directing them to the OAuth server's authorization endpoint. The user logs in and grants (or denies) the requested `scopes`.
</Step>
<Step title="Authorization Code & Token Exchange">
Upon approval, the OAuth server redirects the user's browser to the local callback server with an `authorization_code`. The client captures this code and exchanges it with the OAuth server's token endpoint for an `access_token` (and often a `refresh_token`) using PKCE for security.
</Step>
<Step title="Token Caching">
The obtained tokens are saved to the `token_storage_cache_dir` for future use, eliminating the need for repeated browser interactions.
</Step> 
<Step title="Authenticated Requests">
The access token is automatically included in the `Authorization` header for requests to the MCP server. 
</Step>
<Step title="Refresh Token">
If the access token expires, the client will automatically use the refresh token to get a new access token.
</Step>
</Steps>

## Token Management

### Token Storage

OAuth access tokens are automatically cached in `~/.fastmcp/oauth-mcp-client-cache/` and persist between application runs. Files are keyed by the OAuth server's base URL.

### Managing Cache

To clear the tokens for a specific server, instantiate a `FileTokenStorage` instance and call the `clear` method:

```python
from fastmcp.client.auth.oauth import FileTokenStorage

storage = FileTokenStorage(server_url="https://fastmcp.cloud/mcp")
await storage.clear()
```

To clear *all* tokens for all servers, call the `clear_all` method on the `FileTokenStorage` class:

```python
from fastmcp.client.auth.oauth import FileTokenStorage

FileTokenStorage.clear_all()
```

================
File: docs/clients/client.mdx
================
---
title: The FastMCP Client
sidebarTitle: Overview
description: Programmatic client for interacting with MCP servers through a well-typed, Pythonic interface.
icon: user-robot
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

The central piece of MCP client applications is the `fastmcp.Client` class. This class provides a **programmatic interface** for interacting with any Model Context Protocol (MCP) server, handling protocol details and connection management automatically.

The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for:

- **Testing MCP servers** during development
- **Building deterministic applications** that need reliable MCP interactions  
- **Creating the foundation for agentic or LLM-based clients** with structured, type-safe operations

All client operations require using the `async with` context manager for proper connection lifecycle management.


<Note>
This is not an agentic client - it requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems.
</Note>

## Creating a Client

Creating a client is straightforward. You provide a server source and the client automatically infers the appropriate transport mechanism.

```python
import asyncio
from fastmcp import Client, FastMCP

# In-memory server (ideal for testing)
server = FastMCP("TestServer")
client = Client(server)

# HTTP server
client = Client("https://example.com/mcp")

# Local Python script
client = Client("my_mcp_server.py")

async def main():
    async with client:
        # Basic server interaction
        await client.ping()
        
        # List available operations
        tools = await client.list_tools()
        resources = await client.list_resources()
        prompts = await client.list_prompts()
        
        # Execute operations
        result = await client.call_tool("example_tool", {"param": "value"})
        print(result)

asyncio.run(main())
```

## Client-Transport Architecture

The FastMCP Client separates concerns between protocol and connection:

- **`Client`**: Handles MCP protocol operations (tools, resources, prompts) and manages callbacks
- **`Transport`**: Establishes and maintains the connection (WebSockets, HTTP, Stdio, in-memory)

### Transport Inference

The client automatically infers the appropriate transport based on the input:

1. **`FastMCP` instance** → In-memory transport (perfect for testing)
2. **File path ending in `.py`** → Python Stdio transport
3. **File path ending in `.js`** → Node.js Stdio transport  
4. **URL starting with `http://` or `https://`** → HTTP transport
5. **`MCPConfig` dictionary** → Multi-server client

```python
from fastmcp import Client, FastMCP

# Examples of transport inference
client_memory = Client(FastMCP("TestServer"))
client_script = Client("./server.py") 
client_http = Client("https://api.example.com/mcp")
```

<Tip>
For testing and development, always prefer the in-memory transport by passing a `FastMCP` server directly to the client. This eliminates network complexity and separate processes.
</Tip>

## Configuration-Based Clients

<VersionBadge version="2.4.0" />

Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop.

### Configuration Format

```python
config = {
    "mcpServers": {
        "server_name": {
            # Remote HTTP/SSE server
            "transport": "http",  # or "sse" 
            "url": "https://api.example.com/mcp",
            "headers": {"Authorization": "Bearer token"},
            "auth": "oauth"  # or bearer token string
        },
        "local_server": {
            # Local stdio server
            "transport": "stdio",
            "command": "python",
            "args": ["./server.py", "--verbose"],
            "env": {"DEBUG": "true"},
            "cwd": "/path/to/server",
        }
    }
}
```

### Multi-Server Example

```python
config = {
    "mcpServers": {
        "weather": {"url": "https://weather-api.example.com/mcp"},
        "assistant": {"command": "python", "args": ["./assistant_server.py"]}
    }
}

client = Client(config)

async with client:
    # Tools are prefixed with server names
    weather_data = await client.call_tool("weather_get_forecast", {"city": "London"})
    response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"})
    
    # Resources use prefixed URIs
    icons = await client.read_resource("weather://weather/icons/sunny")
    templates = await client.read_resource("resource://assistant/templates/list")
```

## Connection Lifecycle

The client operates asynchronously and uses context managers for connection management:

```python
async def example():
    client = Client("my_mcp_server.py")
    
    # Connection established here
    async with client:
        print(f"Connected: {client.is_connected()}")
        
        # Make multiple calls within the same session
        tools = await client.list_tools()
        result = await client.call_tool("greet", {"name": "World"})
        
    # Connection closed automatically here
    print(f"Connected: {client.is_connected()}")
```

## Operations

FastMCP clients can interact with several types of server components:

### Tools

Tools are server-side functions that the client can execute with arguments.

```python
async with client:
    # List available tools
    tools = await client.list_tools()
    
    # Execute a tool
    result = await client.call_tool("multiply", {"a": 5, "b": 3})
    print(result[0].text)  # "15"
```

See [Tools](/clients/tools) for detailed documentation.

### Resources

Resources are data sources that the client can read, either static or templated.

```python
async with client:
    # List available resources
    resources = await client.list_resources()
    
    # Read a resource
    content = await client.read_resource("file:///config/settings.json")
    print(content[0].text)
```

See [Resources](/clients/resources) for detailed documentation.

### Prompts

Prompts are reusable message templates that can accept arguments.

```python
async with client:
    # List available prompts
    prompts = await client.list_prompts()
    
    # Get a rendered prompt
    messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]})
    print(messages.messages)
```

See [Prompts](/clients/prompts) for detailed documentation.

### Server Connectivity

Use `ping()` to verify the server is reachable:

```python
async with client:
    await client.ping()
    print("Server is reachable")
```

## Client Configuration

Clients can be configured with additional handlers and settings for specialized use cases.

### Callback Handlers

The client supports several callback handlers for advanced server interactions:

```python
from fastmcp import Client
from fastmcp.client.logging import LogMessage

async def log_handler(message: LogMessage):
    print(f"Server log: {message.data}")

async def progress_handler(progress: float, total: float | None, message: str | None):
    print(f"Progress: {progress}/{total} - {message}")

async def sampling_handler(messages, params, context):
    # Integrate with your LLM service here
    return "Generated response"

client = Client(
    "my_mcp_server.py",
    log_handler=log_handler,
    progress_handler=progress_handler,
    sampling_handler=sampling_handler,
    timeout=30.0
)
```

The `Client` constructor accepts several configuration options:

- `transport`: Transport instance or source for automatic inference  
- `log_handler`: Handle server log messages
- `progress_handler`: Monitor long-running operations
- `sampling_handler`: Respond to server LLM requests
- `roots`: Provide local context to servers
- `timeout`: Default timeout for requests (in seconds)

### Transport Configuration

For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation.

## Next Steps

Explore the detailed documentation for each operation type:

### Core Operations
- **[Tools](/clients/tools)** - Execute server-side functions and handle results
- **[Resources](/clients/resources)** - Access static and templated resources  
- **[Prompts](/clients/prompts)** - Work with message templates and argument serialization

### Advanced Features
- **[Logging](/clients/logging)** - Handle server log messages
- **[Progress](/clients/progress)** - Monitor long-running operations
- **[Sampling](/clients/sampling)** - Respond to server LLM requests
- **[Roots](/clients/roots)** - Provide local context to servers

### Connection Details
- **[Transports](/clients/transports)** - Configure connection methods and parameters
- **[Authentication](/clients/auth/oauth)** - Set up OAuth and bearer token authentication

<Tip>
The FastMCP Client is designed as a foundational tool. Use it directly for deterministic operations, or build higher-level agentic systems on top of its reliable, type-safe interface.
</Tip>

================
File: docs/clients/elicitation.mdx
================
---
title: User Elicitation
sidebarTitle: Elicitation
description: Handle server-initiated user input requests with structured schemas.
icon: message-question
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx";

<VersionBadge version="2.10.0" />

## What is Elicitation?

Elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask users for information as needed - like prompting for missing parameters, requesting clarification, or gathering additional context.

For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"

## How FastMCP Makes Elicitation Easy

FastMCP's client provides a helpful abstraction layer that:

- **Converts JSON schemas to Python types**: The raw MCP protocol uses JSON schemas, but FastMCP automatically converts these to Python dataclasses
- **Provides structured constructors**: Instead of manually building dictionaries that match the schema, you get dataclass constructors that ensure correct structure
- **Handles type conversion**: FastMCP takes care of converting between JSON representations and Python objects
- **Runtime introspection**: You can inspect the generated dataclass fields to understand the expected structure

When you implement an elicitation handler, FastMCP gives you a dataclass type that matches the server's schema, making it easy to create properly structured responses without having to manually parse JSON schemas.

## Elicitation Handler

Provide an `elicitation_handler` function when creating the client. FastMCP automatically converts the server's JSON schema into a Python dataclass type, making it easy to construct the response:

```python
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult

async def elicitation_handler(message: str, response_type: type, params, context):
    # Present the message to the user and collect input
    user_input = input(f"{message}: ")
    
    # Create response using the provided dataclass type
    # FastMCP converted the JSON schema to this Python type for you
    response_data = response_type(value=user_input)
    
    # You can return data directly - FastMCP will implicitly accept the elicitation
    return response_data
    
    # Or explicitly return an ElicitResult for more control
    # return ElicitResult(action="accept", content=response_data)

client = Client(
    "my_mcp_server.py",
    elicitation_handler=elicitation_handler,
)
```

### Handler Parameters

The elicitation handler receives four parameters:

<Card icon="code" title="Elicitation Handler Parameters">
<ResponseField name="message" type="str">
  The prompt message to display to the user
</ResponseField>

<ResponseField name="response_type" type="type">
  A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing and IDE support.
</ResponseField>

<ResponseField name="params" type="ElicitRequestParams">
  The original MCP elicitation request parameters, including the raw JSON schema in `params.requestedSchema` if you need it
</ResponseField>

<ResponseField name="context" type="RequestContext">
  Request context containing metadata about the elicitation request
</ResponseField>
</Card>

### Response Actions

The handler can return data directly (which implicitly accepts the elicitation) or an `ElicitResult` object for more control over the response action:

<Card icon="code" title="ElicitResult Structure">
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
  How the user responded to the elicitation request
</ResponseField>

<ResponseField name="content" type="dataclass instance | dict | None">
  The user's input data (required for "accept", omitted for "decline"/"cancel")
</ResponseField>
</Card>

**Action Types:**
- **`accept`**: User provided valid input - include their data in the `content` field
- **`decline`**: User chose not to provide the requested information - omit `content`  
- **`cancel`**: User cancelled the entire operation - omit `content`

## Basic Example

```python
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult

async def basic_elicitation_handler(message: str, response_type: type, params, context):
    print(f"Server asks: {message}")
    
    # Simple text input for demonstration
    user_response = input("Your response: ")
    
    if not user_response:
        # For non-acceptance, use ElicitResult explicitly
        return ElicitResult(action="decline")
    
    # Use the response_type dataclass to create a properly structured response
    # FastMCP handles the conversion from JSON schema to Python type
    # Return data directly - FastMCP will implicitly accept the elicitation
    return response_type(value=user_response)

client = Client(
    "my_mcp_server.py", 
    elicitation_handler=basic_elicitation_handler
)
```

================
File: docs/clients/logging.mdx
================
---
title: Server Logging
sidebarTitle: Logging
description: Receive and handle log messages from MCP servers.
icon: receipt
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback.

## Log Handler

Provide a `log_handler` function when creating the client:

```python
from fastmcp import Client
from fastmcp.client.logging import LogMessage

async def log_handler(message: LogMessage):
    level = message.level.upper()
    logger = message.logger or 'server'
    data = message.data
    print(f"[{level}] {logger}: {data}")

client = Client(
    "my_mcp_server.py",
    log_handler=log_handler,
)
```

### Handler Parameters

The `log_handler` is called every time a log message is received. It receives a `LogMessage` object:

<Card icon="code" title="Log Handler Parameters">
<ResponseField name="LogMessage" type="Log Message Object">
  <Expandable title="attributes">
    <ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
      The log level
    </ResponseField>

    <ResponseField name="logger" type="str | None">
      The logger name (optional, may be None)
    </ResponseField>

    <ResponseField name="data" type="Any">
      The actual log message content
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

```python
async def detailed_log_handler(message: LogMessage):
    if message.level == "error":
        print(f"ERROR: {message.data}")
    elif message.level == "warning":
        print(f"WARNING: {message.data}")
    else:
        print(f"{message.level.upper()}: {message.data}")
```

## Default Log Handling

If you don't provide a custom `log_handler`, FastMCP uses a default handler that emits a DEBUG-level FastMCP log for every log message received from the server, which is useful for visibility without polluting your own logs.

```python
client = Client("my_mcp_server.py")

async with client:
    # Server logs will be emitted at DEBUG level automatically
    await client.call_tool("some_tool")
```

================
File: docs/clients/messages.mdx
================
---
title: Message Handling
sidebarTitle: Messages
description: Handle MCP messages, requests, and notifications with custom message handlers.
icon: envelope
---

import { VersionBadge } from "/snippets/version-badge.mdx";

<VersionBadge version="2.9.1" />

MCP clients can receive various types of messages from servers, including requests that need responses and notifications that don't. The message handler provides a unified way to process all these messages.

## Function-Based Handler

The simplest way to handle messages is with a function that receives all messages:

```python
from fastmcp import Client

async def message_handler(message):
    """Handle all MCP messages from the server."""
    if hasattr(message, 'root'):
        method = message.root.method
        print(f"Received: {method}")
        
        # Handle specific notifications
        if method == "notifications/tools/list_changed":
            print("Tools have changed - might want to refresh tool cache")
        elif method == "notifications/resources/list_changed":
            print("Resources have changed")

client = Client(
    "my_mcp_server.py",
    message_handler=message_handler,
)
```

## Message Handler Class

For fine-grained targeting, FastMCP provides a `MessageHandler` class you can subclass to take advantage of specific hooks:

```python
from fastmcp import Client
from fastmcp.client.messages import MessageHandler
import mcp.types

class MyMessageHandler(MessageHandler):
    async def on_tool_list_changed(
        self, notification: mcp.types.ToolListChangedNotification
    ) -> None:
        """Handle tool list changes specifically."""
        print("Tool list changed - refreshing available tools")

client = Client(
    "my_mcp_server.py",
    message_handler=MyMessageHandler(),
)
```

### Available Handler Methods

All handler methods receive a single argument - the specific message type:

<Card icon="code" title="Message Handler Methods">
<ResponseField name="on_message(message)" type="Any MCP message">
  Called for ALL messages (requests and notifications)
</ResponseField>

<ResponseField name="on_request(request)" type="mcp.types.ClientRequest">
  Called for requests that expect responses
</ResponseField>

<ResponseField name="on_notification(notification)" type="mcp.types.ServerNotification">
  Called for notifications (fire-and-forget)
</ResponseField>

<ResponseField name="on_tool_list_changed(notification)" type="mcp.types.ToolListChangedNotification">
  Called when the server's tool list changes
</ResponseField>

<ResponseField name="on_resource_list_changed(notification)" type="mcp.types.ResourceListChangedNotification">
  Called when the server's resource list changes
</ResponseField>

<ResponseField name="on_prompt_list_changed(notification)" type="mcp.types.PromptListChangedNotification">
  Called when the server's prompt list changes
</ResponseField>

<ResponseField name="on_progress(notification)" type="mcp.types.ProgressNotification">
  Called for progress updates during long-running operations
</ResponseField>

<ResponseField name="on_logging_message(notification)" type="mcp.types.LoggingMessageNotification">
  Called for log messages from the server
</ResponseField>
</Card>

## Example: Handling Tool Changes

Here's a practical example of handling tool list changes:

```python
from fastmcp.client.messages import MessageHandler
import mcp.types

class ToolCacheHandler(MessageHandler):
    def __init__(self):
        self.cached_tools = []
    
    async def on_tool_list_changed(
        self, notification: mcp.types.ToolListChangedNotification
    ) -> None:
        """Clear tool cache when tools change."""
        print("Tools changed - clearing cache")
        self.cached_tools = []  # Force refresh on next access

client = Client("server.py", message_handler=ToolCacheHandler())
```

## Handling Requests

While the message handler receives server-initiated requests, for most use cases you should use the dedicated callback parameters instead:

- **Sampling requests**: Use [`sampling_handler`](/clients/sampling)
- **Progress requests**: Use [`progress_handler`](/clients/progress)  
- **Log requests**: Use [`log_handler`](/clients/logging)

The message handler is primarily for monitoring and handling notifications rather than responding to requests.

================
File: docs/clients/progress.mdx
================
---
title: Progress Monitoring
sidebarTitle: Progress
description: Handle progress notifications from long-running server operations.
icon: bars-progress
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.3.5" />

MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler.

## Progress Handler

Set a progress handler when creating the client:

```python
from fastmcp import Client

async def my_progress_handler(
    progress: float, 
    total: float | None, 
    message: str | None
) -> None:
    if total is not None:
        percentage = (progress / total) * 100
        print(f"Progress: {percentage:.1f}% - {message or ''}")
    else:
        print(f"Progress: {progress} - {message or ''}")

client = Client(
    "my_mcp_server.py",
    progress_handler=my_progress_handler
)
```

### Handler Parameters

The progress handler receives three parameters:


<Card icon="code" title="Progress Handler Parameters">
<ResponseField name="progress" type="float">
  Current progress value
</ResponseField>

<ResponseField name="total" type="float | None">
  Expected total value (may be None)
</ResponseField>

<ResponseField name="message" type="str | None">
  Optional status message (may be None)
</ResponseField>
</Card>


## Per-Call Progress Handler

Override the progress handler for specific tool calls:

```python
async with client:
    # Override with specific progress handler for this call
    result = await client.call_tool(
        "long_running_task", 
        {"param": "value"}, 
        progress_handler=my_progress_handler
    )
```

================
File: docs/clients/prompts.mdx
================
---
title: Prompts
sidebarTitle: Prompts
description: Use server-side prompt templates with automatic argument serialization.
icon: message-lines
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions.

## Listing Prompts

Use `list_prompts()` to retrieve all available prompt templates:

```python
async with client:
    prompts = await client.list_prompts()
    # prompts -> list[mcp.types.Prompt]
    
    for prompt in prompts:
        print(f"Prompt: {prompt.name}")
        print(f"Description: {prompt.description}")
        if prompt.arguments:
            print(f"Arguments: {[arg.name for arg in prompt.arguments]}")
```

## Using Prompts

### Basic Usage

Request a rendered prompt using `get_prompt()` with the prompt name and arguments:

```python
async with client:
    # Simple prompt without arguments
    result = await client.get_prompt("welcome_message")
    # result -> mcp.types.GetPromptResult
    
    # Access the generated messages
    for message in result.messages:
        print(f"Role: {message.role}")
        print(f"Content: {message.content}")
```

### Prompts with Arguments

Pass arguments as a dictionary to customize the prompt:

```python
async with client:
    # Prompt with simple arguments
    result = await client.get_prompt("user_greeting", {
        "name": "Alice",
        "role": "administrator"
    })
    
    # Access the personalized messages
    for message in result.messages:
        print(f"Generated message: {message.content}")
```

## Automatic Argument Serialization

<VersionBadge version="2.9.0" />

FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. This allows you to pass typed objects directly:

```python
from dataclasses import dataclass

@dataclass
class UserData:
    name: str
    age: int

async with client:
    # Complex arguments are automatically serialized
    result = await client.get_prompt("analyze_user", {
        "user": UserData(name="Alice", age=30),     # Automatically serialized to JSON
        "preferences": {"theme": "dark"},           # Dict serialized to JSON string
        "scores": [85, 92, 78],                     # List serialized to JSON string
        "simple_name": "Bob"                        # Strings passed through unchanged
    })
```

The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers can automatically deserialize these JSON strings back to the expected types.

### Serialization Examples

```python
async with client:
    result = await client.get_prompt("data_analysis", {
        # These will be automatically serialized to JSON strings:
        "config": {
            "format": "csv",
            "include_headers": True,
            "delimiter": ","
        },
        "filters": [
            {"field": "age", "operator": ">", "value": 18},
            {"field": "status", "operator": "==", "value": "active"}
        ],
        # This remains a string:
        "report_title": "Monthly Analytics Report"
    })
```

## Working with Prompt Results

The `get_prompt()` method returns a `GetPromptResult` object containing a list of messages:

```python
async with client:
    result = await client.get_prompt("conversation_starter", {"topic": "climate"})
    
    # Access individual messages
    for i, message in enumerate(result.messages):
        print(f"Message {i + 1}:")
        print(f"  Role: {message.role}")
        print(f"  Content: {message.content.text if hasattr(message.content, 'text') else message.content}")
```

## Raw MCP Protocol Access

For access to the complete MCP protocol objects, use the `*_mcp` methods:

```python
async with client:
    # Raw MCP method returns full protocol object
    prompts_result = await client.list_prompts_mcp()
    # prompts_result -> mcp.types.ListPromptsResult
    
    prompt_result = await client.get_prompt_mcp("example_prompt", {"arg": "value"})
    # prompt_result -> mcp.types.GetPromptResult
```

## Multi-Server Clients

When using multi-server clients, prompts are accessible without prefixing (unlike tools):

```python
async with client:  # Multi-server client
    # Prompts from any server are directly accessible
    result1 = await client.get_prompt("weather_prompt", {"city": "London"})
    result2 = await client.get_prompt("assistant_prompt", {"query": "help"})
```

## Common Prompt Patterns

### System Messages

Many prompts generate system messages for LLM configuration:

```python
async with client:
    result = await client.get_prompt("system_configuration", {
        "role": "helpful assistant",
        "expertise": "python programming"
    })
    
    # Typically returns messages with role="system"
    system_message = result.messages[0]
    print(f"System prompt: {system_message.content}")
```

### Conversation Templates

Prompts can generate multi-turn conversation templates:

```python
async with client:
    result = await client.get_prompt("interview_template", {
        "candidate_name": "Alice",
        "position": "Senior Developer"
    })
    
    # Multiple messages for a conversation flow
    for message in result.messages:
        print(f"{message.role}: {message.content}")
```

<Tip>
Prompt arguments and their expected types depend on the specific prompt implementation. Check the server's documentation or use `list_prompts()` to see available arguments for each prompt.
</Tip>

================
File: docs/clients/resources.mdx
================
---
title: Resource Operations
sidebarTitle: Resources
description: Access static and templated resources from MCP servers.
icon: folder-open
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

Resources are data sources exposed by MCP servers. They can be static files or dynamic templates that generate content based on parameters.

## Types of Resources

MCP servers expose two types of resources:

- **Static Resources**: Fixed content accessible via URI (e.g., configuration files, documentation)
- **Resource Templates**: Dynamic resources that accept parameters to generate content (e.g., API endpoints, database queries)

## Listing Resources

### Static Resources

Use `list_resources()` to retrieve all static resources available on the server:

```python
async with client:
    resources = await client.list_resources()
    # resources -> list[mcp.types.Resource]
    
    for resource in resources:
        print(f"Resource URI: {resource.uri}")
        print(f"Name: {resource.name}")
        print(f"Description: {resource.description}")
        print(f"MIME Type: {resource.mimeType}")
```

### Resource Templates

Use `list_resource_templates()` to retrieve available resource templates:

```python
async with client:
    templates = await client.list_resource_templates()
    # templates -> list[mcp.types.ResourceTemplate]
    
    for template in templates:
        print(f"Template URI: {template.uriTemplate}")
        print(f"Name: {template.name}")
        print(f"Description: {template.description}")
```

## Reading Resources

### Static Resources

Read a static resource using its URI:

```python
async with client:
    # Read a static resource
    content = await client.read_resource("file:///path/to/README.md")
    # content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
    
    # Access text content
    if hasattr(content[0], 'text'):
        print(content[0].text)
    
    # Access binary content
    if hasattr(content[0], 'blob'):
        print(f"Binary data: {len(content[0].blob)} bytes")
```

### Resource Templates

Read from a resource template by providing the URI with parameters:

```python
async with client:
    # Read a resource generated from a template
    # For example, a template like "weather://{{city}}/current"
    weather_content = await client.read_resource("weather://london/current")
    
    # Access the generated content
    print(weather_content[0].text)  # Assuming text JSON response
```

## Content Types

Resources can return different content types:

### Text Resources

```python
async with client:
    content = await client.read_resource("resource://config/settings.json")
    
    for item in content:
        if hasattr(item, 'text'):
            print(f"Text content: {item.text}")
            print(f"MIME type: {item.mimeType}")
```

### Binary Resources

```python
async with client:
    content = await client.read_resource("resource://images/logo.png")
    
    for item in content:
        if hasattr(item, 'blob'):
            print(f"Binary content: {len(item.blob)} bytes")
            print(f"MIME type: {item.mimeType}")
            
            # Save to file
            with open("downloaded_logo.png", "wb") as f:
                f.write(item.blob)
```

## Working with Multi-Server Clients

When using multi-server clients, resource URIs are automatically prefixed with the server name:

```python
async with client:  # Multi-server client
    # Access resources from different servers
    weather_icons = await client.read_resource("weather://weather/icons/sunny")
    templates = await client.read_resource("resource://assistant/templates/list")
    
    print(f"Weather icon: {weather_icons[0].blob}")
    print(f"Templates: {templates[0].text}")
```

## Raw MCP Protocol Access

For access to the complete MCP protocol objects, use the `*_mcp` methods:

```python
async with client:
    # Raw MCP methods return full protocol objects
    resources_result = await client.list_resources_mcp()
    # resources_result -> mcp.types.ListResourcesResult
    
    templates_result = await client.list_resource_templates_mcp()
    # templates_result -> mcp.types.ListResourceTemplatesResult
    
    content_result = await client.read_resource_mcp("resource://example")
    # content_result -> mcp.types.ReadResourceResult
```

## Common Resource URI Patterns

Different MCP servers may use various URI schemes:

```python
# File system resources
"file:///path/to/file.txt"

# Custom protocol resources  
"weather://london/current"
"database://users/123"

# Generic resource protocol
"resource://config/settings"
"resource://templates/email"
```

<Tip>
Resource URIs and their formats depend on the specific MCP server implementation. Check the server's documentation for available resources and their URI patterns.
</Tip>

================
File: docs/clients/roots.mdx
================
---
title: Client Roots
sidebarTitle: Roots
description: Provide local context and resource boundaries to MCP servers.
icon: folder-tree
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

Roots are a way for clients to inform servers about the resources they have access to. Servers can use this information to adjust behavior or provide more relevant responses.

## Setting Static Roots

Provide a list of roots when creating the client:

<CodeGroup>
```python Static Roots
from fastmcp import Client

client = Client(
    "my_mcp_server.py", 
    roots=["/path/to/root1", "/path/to/root2"]
)
```

```python Dynamic Roots Callback
from fastmcp import Client
from fastmcp.client.roots import RequestContext

async def roots_callback(context: RequestContext) -> list[str]:
    print(f"Server requested roots (Request ID: {context.request_id})")
    return ["/path/to/root1", "/path/to/root2"]

client = Client(
    "my_mcp_server.py", 
    roots=roots_callback
)
```
</CodeGroup>

================
File: docs/clients/sampling.mdx
================
---
title: LLM Sampling
sidebarTitle: Sampling
description: Handle server-initiated LLM sampling requests.
icon: robot
---

import { VersionBadge } from "/snippets/version-badge.mdx";

<VersionBadge version="2.0.0" />

MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback.

## Sampling Handler

Provide a `sampling_handler` function when creating the client:

```python
from fastmcp import Client
from fastmcp.client.sampling import (
    SamplingMessage,
    SamplingParams,
    RequestContext,
)

async def sampling_handler(
    messages: list[SamplingMessage],
    params: SamplingParams,
    context: RequestContext
) -> str:
    # Your LLM integration logic here
    # Extract text from messages and generate a response
    return "Generated response based on the messages"

client = Client(
    "my_mcp_server.py",
    sampling_handler=sampling_handler,
)
```

### Handler Parameters

The sampling handler receives three parameters:

<Card icon="code" title="Sampling Handler Parameters">
<ResponseField name="SamplingMessage" type="Sampling Message Object">
  <Expandable title="attributes">
    <ResponseField name="role" type='Literal["user", "assistant"]'>
      The role of the message.
    </ResponseField>

    <ResponseField name="content" type="TextContent | ImageContent | AudioContent">
      The content of the message.

      TextContent is most common, and has a `.text` attribute.
    </ResponseField>

  </Expandable>
</ResponseField>
<ResponseField name="SamplingParams" type="Sampling Parameters Object">
  <Expandable title="attributes">
    <ResponseField name="messages" type="list[SamplingMessage]">
      The messages to sample from
    </ResponseField>

    <ResponseField name="modelPreferences" type="ModelPreferences | None">
      The server's preferences for which model to select. The client MAY ignore
    these preferences.
    <Expandable title="attributes">
      <ResponseField name="hints" type="list[ModelHint] | None">
        The hints to use for model selection.
      </ResponseField>

      <ResponseField name="costPriority" type="float | None">
        The cost priority for model selection.
      </ResponseField>

      <ResponseField name="speedPriority" type="float | None">
        The speed priority for model selection.
      </ResponseField>

      <ResponseField name="intelligencePriority" type="float | None">
        The intelligence priority for model selection.
      </ResponseField>
    </Expandable>
    </ResponseField>

    <ResponseField name="systemPrompt" type="str | None">
      An optional system prompt the server wants to use for sampling.
    </ResponseField>

    <ResponseField name="includeContext" type="IncludeContext | None">
      A request to include context from one or more MCP servers (including the caller), to
      be attached to the prompt.
    </ResponseField>

    <ResponseField name="temperature" type="float | None">
      The sampling temperature.
    </ResponseField>

    <ResponseField name="maxTokens" type="int">
      The maximum number of tokens to sample.
    </ResponseField>

    <ResponseField name="stopSequences" type="list[str] | None">
      The stop sequences to use for sampling.
    </ResponseField>

    <ResponseField name="metadata" type="dict[str, Any] | None">
      Optional metadata to pass through to the LLM provider.
    </ResponseField>
    </Expandable>

</ResponseField>
<ResponseField name="RequestContext" type="Request Context Object">
  <Expandable title="attributes">
    <ResponseField name="request_id" type="RequestId">
      Unique identifier for the MCP request
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

## Basic Example

```python
from fastmcp import Client
from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext

async def basic_sampling_handler(
    messages: list[SamplingMessage],
    params: SamplingParams,
    context: RequestContext
) -> str:
    # Extract message content
    conversation = []
    for message in messages:
        content = message.content.text if hasattr(message.content, 'text') else str(message.content)
        conversation.append(f"{message.role}: {content}")

    # Use the system prompt if provided
    system_prompt = params.systemPrompt or "You are a helpful assistant."

    # Here you would integrate with your preferred LLM service
    # This is just a placeholder response
    return f"Response based on conversation: {' | '.join(conversation)}"

client = Client(
    "my_mcp_server.py",
    sampling_handler=basic_sampling_handler
)
```

================
File: docs/clients/tools.mdx
================
---
title: Tool Operations
sidebarTitle: Tools
description: Discover and execute server-side tools with the FastMCP client.
icon: wrench
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments.

## Discovering Tools

Use `list_tools()` to retrieve all tools available on the server:

```python
async with client:
    tools = await client.list_tools()
    # tools -> list[mcp.types.Tool]
    
    for tool in tools:
        print(f"Tool: {tool.name}")
        print(f"Description: {tool.description}")
        if tool.inputSchema:
            print(f"Parameters: {tool.inputSchema}")
```

## Executing Tools

### Basic Execution

Execute a tool using `call_tool()` with the tool name and arguments:

```python
async with client:
    # Simple tool call
    result = await client.call_tool("add", {"a": 5, "b": 3})
    # result -> CallToolResult with structured and unstructured data
    
    # Access structured data (automatically deserialized)
    print(result.data)  # 8 (int) or {"result": 8} for primitive types
    
    # Access traditional content blocks  
    print(result.content[0].text)  # "8" (TextContent)
```

### Advanced Execution Options

The `call_tool()` method supports additional parameters for timeout control and progress monitoring:

```python
async with client:
    # With timeout (aborts if execution takes longer than 2 seconds)
    result = await client.call_tool(
        "long_running_task", 
        {"param": "value"}, 
        timeout=2.0
    )
    
    # With progress handler (to track execution progress)
    result = await client.call_tool(
        "long_running_task",
        {"param": "value"},
        progress_handler=my_progress_handler
    )
```

**Parameters:**
- `name`: The tool name (string)
- `arguments`: Dictionary of arguments to pass to the tool (optional)
- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout)
- `progress_handler`: Progress callback function (optional, overrides client-level handler)

## Handling Results

<VersionBadge version="2.10.0" />

Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes.

### CallToolResult Properties

<Card icon="code" title="CallToolResult Properties">
<ResponseField name=".data" type="Any">
  **FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas.
</ResponseField>

<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
  Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers.
</ResponseField>

<ResponseField name=".structured_content" type="dict[str, Any] | None">
  Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs.
</ResponseField>

<ResponseField name=".is_error" type="bool">
  Boolean indicating if the tool execution failed.
</ResponseField>
</Card>

### Structured Data Access

FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction:

```python
from datetime import datetime
from uuid import UUID

async with client:
    result = await client.call_tool("get_weather", {"city": "London"})
    
    # FastMCP reconstructs complete Python objects from the server's output schema
    weather = result.data  # Server-defined WeatherReport object
    print(f"Temperature: {weather.temperature}°C at {weather.timestamp}")
    print(f"Station: {weather.station_id}")
    print(f"Humidity: {weather.humidity}%")
    
    # The timestamp is a real datetime object, not a string!
    assert isinstance(weather.timestamp, datetime)
    assert isinstance(weather.station_id, UUID)
    
    # Compare with raw structured JSON (standard MCP)
    print(f"Raw JSON: {result.structured_content}")
    # {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."}
    
    # Traditional content blocks (standard MCP)  
    print(f"Text content: {result.content[0].text}")
```

### Fallback Behavior

For tools without output schemas or when deserialization fails, `.data` will be `None`:

```python
async with client:
    result = await client.call_tool("legacy_tool", {"param": "value"})
    
    if result.data is not None:
        # Structured output available and successfully deserialized
        print(f"Structured: {result.data}")
    else:
        # No structured output or deserialization failed - use content blocks
        for content in result.content:
            if hasattr(content, 'text'):
                print(f"Text result: {content.text}")
            elif hasattr(content, 'data'):
                print(f"Binary data: {len(content.data)} bytes")
```

### Primitive Type Unwrapping

<Tip>
FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object.
</Tip>

```python
async with client:
    result = await client.call_tool("calculate_sum", {"a": 5, "b": 3})
    
    # FastMCP client automatically unwraps for convenience
    print(result.data)  # 8 (int) - the original value
    
    # Raw structured content shows the server-side wrapping
    print(result.structured_content)  # {"result": 8}
    
    # Other MCP clients would need to manually access ["result"]
    # value = result.structured_content["result"]  # Not needed with FastMCP!
```

## Error Handling

### Exception-Based Error Handling

By default, `call_tool()` raises a `ToolError` if the tool execution fails:

```python
from fastmcp.exceptions import ToolError

async with client:
    try:
        result = await client.call_tool("potentially_failing_tool", {"param": "value"})
        print("Tool succeeded:", result.data)
    except ToolError as e:
        print(f"Tool failed: {e}")
```

### Manual Error Checking

You can disable automatic error raising and manually check the result:

```python
async with client:
    result = await client.call_tool(
        "potentially_failing_tool", 
        {"param": "value"}, 
        raise_on_error=False
    )
    
    if result.is_error:
        print(f"Tool failed: {result.content[0].text}")
    else:
        print(f"Tool succeeded: {result.data}")
```

### Raw MCP Protocol Access

For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:

```python
async with client:
    result = await client.call_tool_mcp("potentially_failing_tool", {"param": "value"})
    # result -> mcp.types.CallToolResult
    
    if result.isError:
        print(f"Tool failed: {result.content}")
    else:
        print(f"Tool succeeded: {result.content}")
        # Note: No automatic deserialization with call_tool_mcp()
```

## Argument Handling

Arguments are passed as a dictionary to the tool:

```python
async with client:
    # Simple arguments
    result = await client.call_tool("greet", {"name": "World"})
    
    # Complex arguments
    result = await client.call_tool("process_data", {
        "config": {"format": "json", "validate": True},
        "items": [1, 2, 3, 4, 5],
        "metadata": {"source": "api", "version": "1.0"}
    })
```

<Tip>
For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
</Tip>

================
File: docs/clients/transports.mdx
================
---
title: Client Transports
sidebarTitle: Transports
description: Understand the different ways FastMCP Clients can connect to servers.
icon: link
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.0.0" />

The FastMCP `Client` relies on a `ClientTransport` object to handle the specifics of connecting to and communicating with an MCP server. FastMCP provides several built-in transport implementations for common connection methods.

While the `Client` often infers the correct transport automatically (see [Client Overview](/clients/client#transport-inference)), you can also instantiate transports explicitly for more control.

<Tip>
Clients are lightweight objects, so don't hesitate to create new ones as needed. However, be mindful of the context management - each time you open a client context (`async with client:`), a new connection or process starts. For best performance, keep client contexts open while performing multiple operations rather than repeatedly opening and closing them.
</Tip>

## Choosing a Transport

Choose the transport that best fits your use case:

- **Connecting to Remote/Persistent Servers:** Use `StreamableHttpTransport` (recommended, default for HTTP URLs) or `SSETransport` (legacy option) for web-based deployments.

- **Local Development/Testing:** Use `FastMCPTransport` for in-memory, same-process testing of your FastMCP servers.

- **Running Local Servers:** Use `UvxStdioTransport` (Python/uv) or `NpxStdioTransport` (Node/npm) if you need to run MCP servers as packaged tools.

## Network Transports

These transports connect to servers running over a network, typically long-running services accessible via URLs.

### Streamable HTTP

<VersionBadge version="2.3.0" />

Streamable HTTP is the recommended transport for web-based deployments, providing efficient bidirectional communication over HTTP.

#### Overview

- **Class:** `fastmcp.client.transports.StreamableHttpTransport`
- **Inferred From:** URLs starting with `http://` or `https://` (default for HTTP URLs since v2.3.0) that do not contain `/sse/` in the path
- **Server Compatibility:** Works with FastMCP servers running in `http` mode

#### Basic Usage

The simplest way to use Streamable HTTP is to let the transport be inferred from a URL:

```python
from fastmcp import Client
import asyncio

# The Client automatically uses StreamableHttpTransport for HTTP URLs
client = Client("https://example.com/mcp")

async def main():
    async with client:
        tools = await client.list_tools()
        print(f"Available tools: {tools}")

asyncio.run(main())
```

You can also explicitly instantiate the transport:

```python
from fastmcp.client.transports import StreamableHttpTransport

transport = StreamableHttpTransport(url="https://example.com/mcp")
client = Client(transport)
```

#### Authentication with Headers

For servers requiring authentication:

```python
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

# Create transport with authentication headers
transport = StreamableHttpTransport(
    url="https://example.com/mcp",
    headers={"Authorization": "Bearer your-token-here"}
)

client = Client(transport)
```

### SSE (Server-Sent Events)

<VersionBadge version="2.0.0" />

Server-Sent Events (SSE) is a transport that allows servers to push data to clients over HTTP connections. While still supported, Streamable HTTP is now the recommended transport for new web-based deployments.

#### Overview

- **Class:** `fastmcp.client.transports.SSETransport`
- **Inferred From:** HTTP URLs containing `/sse/` in the path
- **Server Compatibility:** Works with FastMCP servers running in `sse` mode

#### Basic Usage

The simplest way to use SSE is to let the transport be inferred from a URL with `/sse/` in the path:

```python
from fastmcp import Client
import asyncio

# The Client automatically uses SSETransport for URLs containing /sse/ in the path
client = Client("https://example.com/sse")

async def main():
    async with client:
        tools = await client.list_tools()
        print(f"Available tools: {tools}")

asyncio.run(main())
```

You can also explicitly instantiate the transport for URLs that do not contain `/sse/` in the path or for more control: 

```python
from fastmcp.client.transports import SSETransport

transport = SSETransport(url="https://example.com/sse")
client = Client(transport)
```

#### Authentication with Headers

SSE transport also supports custom headers for authentication:

```python
from fastmcp import Client
from fastmcp.client.transports import SSETransport

# Create SSE transport with authentication headers
transport = SSETransport(
    url="https://example.com/sse",
    headers={"Authorization": "Bearer your-token-here"}
)

client = Client(transport)
```

#### When to Use SSE vs. Streamable HTTP

- **Use Streamable HTTP when:**
  - Setting up new deployments (recommended default)
  - You need bidirectional streaming
  - You're connecting to FastMCP servers running in `http` mode

- **Use SSE when:**
  - Connecting to legacy FastMCP servers running in `sse` mode
  - Working with infrastructure optimized for Server-Sent Events

## Local Transports

These transports manage an MCP server running as a subprocess, communicating with it via standard input (stdin) and standard output (stdout). This is the standard mechanism used by clients like Claude Desktop.

### Session Management

All stdio transports support a `keep_alive` parameter (default: `True`) that controls session persistence across multiple client context managers:

- **`keep_alive=True` (default)**: The subprocess and session are maintained between client context exits and re-entries. This improves performance when making multiple separate connections to the same server.
- **`keep_alive=False`**: A new subprocess is started for each client context, ensuring complete isolation between sessions.

When `keep_alive=True`, you can manually close the session using `await client.close()` if needed. This will terminate the subprocess and require a new one to be started on the next connection.

<CodeGroup>
```python keep_alive=True
from fastmcp import Client

# Client with keep_alive=True (default)
client = Client("my_mcp_server.py")

async def example():
    # First session
    async with client:
        await client.ping()

    # Second session - uses the same subprocess
    async with client:
        await client.ping()

    # Manually close the session
    await client.close()

    # Third session - will start a new subprocess
    async with client:
        await client.ping()

asyncio.run(example())
```
```python keep_alive=False
from fastmcp import Client

# Client with keep_alive=False
client = Client("my_mcp_server.py", keep_alive=False)

async def example():
    # First session
    async with client:
        await client.ping()
    
    # Second session - will start a new subprocess
    async with client:
        await client.ping()

    # Third session - will start a new subprocess
    async with client:
        await client.ping()

asyncio.run(example())
```
</CodeGroup>

### Python Stdio

- **Class:** `fastmcp.client.transports.PythonStdioTransport`
- **Inferred From:** Paths to `.py` files
- **Use Case:** Running a Python-based MCP server script in a subprocess

This is the most common way to interact with local FastMCP servers during development or when integrating with tools that expect to launch a server script.

```python
from fastmcp import Client
from fastmcp.client.transports import PythonStdioTransport

server_script = "my_mcp_server.py" # Path to your server script

# Option 1: Inferred transport
client = Client(server_script)

# Option 2: Explicit transport with custom configuration
transport = PythonStdioTransport(
    script_path=server_script,
    python_cmd="/usr/bin/python3.11", # Optional: specify Python interpreter
    # args=["--some-server-arg"],      # Optional: pass arguments to the script
    # env={"MY_VAR": "value"},         # Optional: set environment variables
)
client = Client(transport)

async def main():
    async with client:
        tools = await client.list_tools()
        print(f"Connected via Python Stdio, found tools: {tools}")

asyncio.run(main())
```

<Warning>
The server script must include logic to start the MCP server and listen on stdio, typically via `mcp.run()` or `fastmcp.server.run()`. The Client only launches the script; it doesn't inject the server logic.
</Warning>

### Node.js Stdio

- **Class:** `fastmcp.client.transports.NodeStdioTransport`
- **Inferred From:** Paths to `.js` files
- **Use Case:** Running a Node.js-based MCP server script in a subprocess

Similar to the Python transport, but for JavaScript servers.

```python
from fastmcp import Client
from fastmcp.client.transports import NodeStdioTransport

node_server_script = "my_mcp_server.js" # Path to your Node.js server script

# Option 1: Inferred transport
client = Client(node_server_script)

# Option 2: Explicit transport
transport = NodeStdioTransport(
    script_path=node_server_script,
    node_cmd="node", # Optional: specify path to Node executable
)
client = Client(transport)

async def main():
    async with client:
        tools = await client.list_tools()
        print(f"Connected via Node.js Stdio, found tools: {tools}")

asyncio.run(main())
```

### UVX Stdio (Experimental)

- **Class:** `fastmcp.client.transports.UvxStdioTransport`
- **Inferred From:** Not automatically inferred
- **Use Case:** Running an MCP server packaged as a Python tool using [`uvx`](https://docs.astral.sh/uv/reference/cli/#uvx)

This is useful for executing MCP servers distributed as command-line tools or packages without installing them into your environment.

```python
from fastmcp import Client
from fastmcp.client.transports import UvxStdioTransport

# Run a hypothetical 'cloud-analyzer-mcp' tool via uvx
transport = UvxStdioTransport(
    tool_name="cloud-analyzer-mcp",
    # from_package="cloud-analyzer-cli", # Optional: specify package if tool name differs
    # with_packages=["boto3", "requests"] # Optional: add dependencies
)
client = Client(transport)

async def main():
    async with client:
        result = await client.call_tool("analyze_bucket", {"name": "my-data"})
        print(f"Analysis result: {result}")

asyncio.run(main())
```

### NPX Stdio (Experimental)

- **Class:** `fastmcp.client.transports.NpxStdioTransport`
- **Inferred From:** Not automatically inferred
- **Use Case:** Running an MCP server packaged as an NPM package using `npx`

Similar to `UvxStdioTransport`, but for the Node.js ecosystem.

```python
from fastmcp import Client
from fastmcp.client.transports import NpxStdioTransport

# Run an MCP server from an NPM package
transport = NpxStdioTransport(
    package="mcp-server-package",
    # args=["--port", "stdio"] # Optional: pass arguments to the package
)
client = Client(transport)

async def main():
    async with client:
        result = await client.call_tool("get_npm_data", {})
        print(f"Result: {result}")

asyncio.run(main())
```

## In-Memory Transports

### FastMCP Transport

- **Class:** `fastmcp.client.transports.FastMCPTransport`
- **Inferred From:** An instance of `fastmcp.server.FastMCP` or a **FastMCP 1.0 server** (`mcp.server.fastmcp.FastMCP`)
- **Use Case:** Connecting directly to a FastMCP server instance in the same Python process

This is extremely useful for testing your FastMCP servers.

```python
from fastmcp import FastMCP, Client
import asyncio

# 1. Create your FastMCP server instance
server = FastMCP(name="InMemoryServer")

@server.tool
def ping(): 
    return "pong"

# 2. Create a client pointing directly to the server instance
client = Client(server)  # Transport is automatically inferred

async def main():
    async with client:
        result = await client.call_tool("ping")
        print(f"In-memory call result: {result}")

asyncio.run(main())
```

Communication happens through efficient in-memory queues, making it very fast and ideal for unit testing. 

## Configuration-Based Transports

### MCPConfig Transport

<VersionBadge version="2.4.0" />

- **Class:** `fastmcp.client.transports.MCPConfigTransport`
- **Inferred From:** An instance of `MCPConfig` or a dictionary matching the MCPConfig schema
- **Use Case:** Connecting to one or more MCP servers defined in a configuration object

MCPConfig follows an emerging standard for MCP server configuration but is subject to change as the specification evolves. The standard supports both local servers (running via stdio) and remote servers (accessed via HTTP).

```python
from fastmcp import Client

# Configuration for multiple MCP servers (both local and remote)
config = {
    "mcpServers": {
        # Remote HTTP server
        "weather": {
            "url": "https://weather-api.example.com/mcp",
            "transport": "http"
        },
        # Local stdio server
        "assistant": {
            "command": "python",
            "args": ["./assistant_server.py"],
            "env": {"DEBUG": "true"}
        },
        # Another remote server
        "calendar": {
            "url": "https://calendar-api.example.com/mcp",
            "transport": "http"
        }
    }
}

# Create a transport from the config (happens automatically with Client)
client = Client(config)

async def main():
    async with client:
        # Tools are accessible with server name prefixes
        weather = await client.call_tool("weather_get_forecast", {"city": "London"})
        answer = await client.call_tool("assistant_answer_question", {"query": "What is MCP?"})
        events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})
        
        # Resources use prefixed URI paths
        icons = await client.read_resource("weather://weather/icons/sunny")
        docs = await client.read_resource("resource://assistant/docs/mcp")

asyncio.run(main())
```

If your configuration has only a single server, the client will connect directly to that server without any prefixing. This makes it convenient to switch between single and multi-server configurations without changing your client code.

<Note>
The MCPConfig format is an emerging standard for MCP server configuration and may change as the MCP ecosystem evolves. While FastMCP aims to maintain compatibility with future versions, be aware that field names or structure might change.
</Note>

================
File: docs/community/README.md
================
# Community Section

This directory contains community-contributed content and showcases for FastMCP.

## Structure

- `showcase.mdx` - Main community showcase page featuring high-quality projects and examples

## Adding Content

To add new community content:
1. Create a new MDX file in this directory
2. Update `docs.json` to include it in the navigation
3. Follow the existing format for consistency

## Guidelines

Community content should:
- Demonstrate best practices
- Provide educational value
- Include proper documentation
- Be maintained and up-to-date

================
File: docs/community/showcase.mdx
================
---
title: 'Community Showcase'
description: 'High-quality projects and examples from the FastMCP community'
icon: 'users'
---

import { YouTubeEmbed } from '/snippets/youtube-embed.mdx'

## Featured Projects

Discover exemplary MCP servers and implementations created by our community. These projects demonstrate best practices and innovative uses of FastMCP.

### Learning Resources

<Card title="MCP Dummy Server" icon="graduation-cap" href="https://github.com/WaiYanNyeinNaing/mcp-dummy-server">
  A comprehensive educational example demonstrating FastMCP best practices with professional dual-transport server implementation, interactive test client, and detailed documentation.
</Card>

#### Video Tutorials

**Build Remote MCP Servers w/ Python & FastMCP** - Claude Integrations Tutorial by Greg + Code

<YouTubeEmbed 
  videoId="bOYkbXP-GGo" 
  title="Build Remote MCP Servers w/ Python & FastMCP" 
/>

**FastMCP — the best way to build an MCP server with Python** - Tutorial by ZazenCodes

<YouTubeEmbed 
  videoId="rnljvmHorQw" 
  title="FastMCP — the best way to build an MCP server with Python" 
/>

**Speedrun a MCP server for Claude Desktop (fastmcp)** - Tutorial by Nate from Prefect

<YouTubeEmbed 
  videoId="67ZwpkUEtSI" 
  title="Speedrun a MCP server for Claude Desktop (fastmcp)" 
/>

### Community Examples

Have you built something interesting with FastMCP? We'd love to feature high-quality examples here! Start a [discussion on GitHub](https://github.com/jlowin/fastmcp/discussions) to share your project.

## Contributing

To get your project featured:

1. Ensure your project demonstrates best practices
2. Include comprehensive documentation
3. Add clear usage examples
4. Open a discussion in our [GitHub Discussions](https://github.com/jlowin/fastmcp/discussions)

We review submissions regularly and feature projects that provide value to the FastMCP community.

## Further Reading

- [Contrib Modules](/integrations/contrib) - Community-contributed modules that are distributed with FastMCP itself

================
File: docs/css/banner.css
================
/* Banner styling -- improve readability with better contrast */
#banner {
#banner::before {
.dark #banner {
.dark #banner::before {
⋮----
#banner * {
.dark #banner * {

================
File: docs/css/python-sdk.css
================
a:has(svg.icon) {

================
File: docs/css/style.css
================
/* Code highlighting -- target only inline code elements, not code blocks */
p code:not(pre code),

================
File: docs/css/version-badge.css
================
/* Version badge -- display a badge with the current version of the documentation */
.version-badge {
.version-badge-container {
.version-badge:hover {
.dark .version-badge {

================
File: docs/deployment/asgi.mdx
================
---
title: Integrating FastMCP in ASGI Applications
sidebarTitle: ASGI Integration
description: Integrate FastMCP servers into existing Starlette, FastAPI, or other ASGI applications
icon: plug
---

import { VersionBadge } from '/snippets/version-badge.mdx'


While FastMCP provides standalone server capabilities, you can also integrate your FastMCP server into existing web applications. This approach is useful for:

- Adding MCP functionality to an existing website or API
- Mounting MCP servers under specific URL paths
- Combining multiple services in a single application
- Leveraging existing authentication and middleware

Please note that all FastMCP servers have a `run()` method that can be used to start the server. This guide focuses on integration with broader ASGI frameworks.

## ASGI Server

FastMCP servers can be created as [Starlette](https://www.starlette.io/) ASGI apps for straightforward hosting or integration into existing applications.

The first step is to obtain a Starlette application instance from your FastMCP server using the `http_app()` method:

<Tip>
The `http_app()` method is new in FastMCP 2.3.2. In older versions, use `sse_app()` for SSE transport or `streamable_http_app()` for Streamable HTTP transport.
</Tip>

```python
from fastmcp import FastMCP

mcp = FastMCP("MyServer")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

# Get a Starlette app instance for Streamable HTTP transport (recommended)
http_app = mcp.http_app()

# For legacy SSE transport (deprecated)
sse_app = mcp.http_app(transport="sse")
```

Both approaches return a Starlette application that can be integrated with other ASGI-compatible web frameworks.

The returned app stores the `FastMCP` instance on `app.state.fastmcp_server`, so you
can access it from custom middleware or routes via `request.app.state.fastmcp_server`.

The MCP server's endpoint is mounted at the root path `/mcp/` for Streamable HTTP transport, and `/sse/` for SSE transport, though you can change these paths by passing a `path` argument to the `http_app()` method:

```python
# For Streamable HTTP transport
http_app = mcp.http_app(path="/custom-mcp-path")

# For SSE transport (deprecated)
sse_app = mcp.http_app(path="/custom-sse-path", transport="sse")
```

### Running the Server

To run the FastMCP server, you can use the `uvicorn` ASGI server:

```python
from fastmcp import FastMCP
import uvicorn

mcp = FastMCP("MyServer")

http_app = mcp.http_app()

if __name__ == "__main__":
    uvicorn.run(http_app, host="0.0.0.0", port=8000)
```

Or, from the command line:

```bash
uvicorn path.to.your.app:http_app --host 0.0.0.0 --port 8000
```

### Custom Middleware

<VersionBadge version="2.3.2" />

You can add custom Starlette middleware to your FastMCP ASGI apps by passing a list of middleware instances to the app creation methods:

```python
from fastmcp import FastMCP
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware

# Create your FastMCP server
mcp = FastMCP("MyServer")

# Define custom middleware
custom_middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["https://example.com", "https://app.example.com"],
        allow_credentials=True,
        allow_methods=["GET", "POST", "OPTIONS"],
        allow_headers=["Content-Type", "Authorization"],
    ),
]

# Create ASGI app with custom middleware
http_app = mcp.http_app(middleware=custom_middleware)
```


## Starlette Integration

<VersionBadge version="2.3.1" />

You can mount your FastMCP server in another Starlette application:

```python
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount

# Create your FastMCP server as well as any tools, resources, etc.
mcp = FastMCP("MyServer")

# Create the ASGI app
mcp_app = mcp.http_app(path='/mcp')

# Create a Starlette app and mount the MCP server
app = Starlette(
    routes=[
        Mount("/mcp-server", app=mcp_app),
        # Add other routes as needed
    ],
    lifespan=mcp_app.lifespan,
)
```

The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting Starlette app.

<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the resulting Starlette app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
</Warning>

### Nested Mounts


You can create complex routing structures by nesting mounts:

```python
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount

# Create your FastMCP server as well as any tools, resources, etc.
mcp = FastMCP("MyServer")

# Create the ASGI app
mcp_app = mcp.http_app(path='/mcp')

# Create nested application structure
inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)])
app = Starlette(
    routes=[Mount("/outer", app=inner_app)],
    lifespan=mcp_app.lifespan,
)
```

In this setup, the MCP server is accessible at the `/outer/inner/mcp/` path of the resulting Starlette app.

<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the *outer* Starlette app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
</Warning>
## FastAPI Integration

<VersionBadge version="2.3.1" />

FastAPI is built on Starlette, so you can mount your FastMCP server in a similar way:

```python
from fastmcp import FastMCP
from fastapi import FastAPI
from starlette.routing import Mount

# Create your FastMCP server as well as any tools, resources, etc.
mcp = FastMCP("MyServer")

# Create the ASGI app
mcp_app = mcp.http_app(path='/mcp')

# Create a FastAPI app and mount the MCP server
app = FastAPI(lifespan=mcp_app.lifespan)
app.mount("/mcp-server", mcp_app)
```

The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting FastAPI app.

<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the resulting FastAPI app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
</Warning>


## Custom Routes

In addition to adding your FastMCP server to an existing ASGI app, you can also add custom web routes to your FastMCP server, which will be exposed alongside the MCP endpoint. To do so, use the `@custom_route` decorator. Note that this is less flexible than using a full ASGI framework, but can be useful for adding simple endpoints like health checks to your standalone server.

```python
from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import PlainTextResponse

mcp = FastMCP("MyServer")

@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> PlainTextResponse:
    return PlainTextResponse("OK")
```

These routes will be included in the FastMCP app when mounted in your web application.

================
File: docs/deployment/running-server.mdx
================
---
title: Running Your FastMCP Server
sidebarTitle: Running the Server
description: Learn how to run and deploy your FastMCP server using various transport protocols like STDIO, Streamable HTTP, and SSE.
icon: circle-play
---
import { VersionBadge } from '/snippets/version-badge.mdx'


FastMCP servers can be run in different ways depending on your application's needs, from local command-line tools to persistent web services. This guide covers the primary methods for running your server, focusing on the available transport protocols: STDIO, Streamable HTTP, and SSE.

## The `run()` Method

FastMCP servers can be run directly from Python by calling the `run()` method on a `FastMCP` instance.

<Tip>
For maximum compatibility, it's best practice to place the `run()` call within an `if __name__ == "__main__":` block. This ensures the server starts only when the script is executed directly, not when imported as a module.
</Tip>

```python {9-10} my_server.py
from fastmcp import FastMCP

mcp = FastMCP(name="MyServer")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()
```
You can now run this MCP server by executing `python my_server.py`.

MCP servers can be run with a variety of different transport options, depending on your application's requirements. The `run()` method can take a `transport` argument and other transport-specific keyword arguments to configure how the server operates.

## The FastMCP CLI

FastMCP also provides a command-line interface for running servers without modifying the source code. After installing FastMCP, you can run your server directly from the command line:

```bash
fastmcp run server.py
```

<Tip>
**Important**: When using `fastmcp run`, it **ignores** the `if __name__ == "__main__"` block entirely. Instead, it looks for a FastMCP object named `mcp`, `server`, or `app` and calls its `run()` method directly with the transport options you specify.

This means you can use `fastmcp run` to override the transport specified in your code, which is particularly useful for testing or changing deployment methods without modifying the code.
</Tip>

You can specify transport options and other configuration:

```bash
fastmcp run server.py --transport sse --port 9000
```

For development and testing, you can use the `dev` command to run your server with the MCP Inspector:

```bash
fastmcp dev server.py
```

See the [CLI documentation](/patterns/cli) for detailed information about all available commands and options.

### Passing Arguments to Servers

When servers accept command line arguments (using argparse, click, or other libraries), you can pass them after `--`:

```bash
fastmcp run config_server.py -- --config config.json
fastmcp run database_server.py -- --database-path /tmp/db.sqlite --debug
```

This is useful for servers that need configuration files, database paths, API keys, or other runtime options.

## Transport Options

Below is a comparison of available transport options to help you choose the right one for your needs:

| Transport | Use Cases | Recommendation |
| --------- | --------- | -------------- |
| **STDIO** | Local tools, command-line scripts, and integrations with clients like Claude Desktop | Best for local tools and when clients manage server processes |
| **Streamable HTTP** | Web-based deployments, microservices, exposing MCP over a network | Recommended choice for web-based deployments |
| **SSE** | Existing web-based deployments that rely on SSE | Deprecated - prefer Streamable HTTP for new projects |

### STDIO

The STDIO transport is the default and most widely compatible option for local MCP server execution. It is ideal for local tools, command-line integrations, and clients like Claude Desktop. However, it has the disadvantage of having to run the MCP code locally, which can introduce security concerns with third-party servers.

STDIO is the default transport, so you don't need to specify it when calling `run()`. However, you can specify it explicitly to make your intent clear:

```python {6}
from fastmcp import FastMCP

mcp = FastMCP()

if __name__ == "__main__":
    mcp.run(transport="stdio")
```

When using Stdio transport, you will typically *not* run the server yourself as a separate process. Rather, your *clients* will spin up a new server process for each session. As such, no additional configuration is required.

### Streamable HTTP

<VersionBadge version="2.3.0" />

Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments.

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/`).
<CodeGroup>
```python {6} server.py
from fastmcp import FastMCP

mcp = FastMCP()

if __name__ == "__main__":
    mcp.run(transport="http")
```
```python {5} client.py
import asyncio
from fastmcp import Client

async def example():
    async with Client("http://127.0.0.1:8000/mcp/") as client:
        await client.ping()

if __name__ == "__main__":
    asyncio.run(example())
```
</CodeGroup>

<Tip>
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.
</Tip>

To customize the host, port, path, or log level, provide appropriate keyword arguments to the `run()` method.

<CodeGroup>
```python {8-11} server.py
from fastmcp import FastMCP

mcp = FastMCP()

if __name__ == "__main__":
    mcp.run(
        transport="http",
        host="127.0.0.1",
        port=4200,
        path="/my-custom-path",
        log_level="debug",
    )
```
```python {5} client.py
import asyncio
from fastmcp import Client

async def example():
    async with Client("http://127.0.0.1:4200/my-custom-path") as client:
        await client.ping()

if __name__ == "__main__":
    asyncio.run(example())
```
</CodeGroup>

### SSE

<Warning>
The SSE transport is deprecated and may be removed in a future version.
New applications should use Streamable HTTP transport instead.
</Warning>

Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP still supports SSE, it is deprecated and Streamable HTTP is preferred for new projects.

To run a server using SSE, you can use the `run()` method with the `transport` argument set to `"sse"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and with default SSE path (`/sse/`) and message path (`/messages/`).

<CodeGroup>
```python {6} server.py
from fastmcp import FastMCP

mcp = FastMCP()

if __name__ == "__main__":
    mcp.run(transport="sse")
```
```python {3,7} client.py
import asyncio
from fastmcp import Client
from fastmcp.client.transports import SSETransport

async def example():
    async with Client(
        transport=SSETransport("http://127.0.0.1:8000/sse/")
    ) as client:
        await client.ping()

if __name__ == "__main__":
    asyncio.run(example())
```
</CodeGroup>

<Tip>
Notice that the client in the above example uses an explicit `SSETransport` to connect to the server. FastMCP will attempt to infer the appropriate transport from the provided configuration, but HTTP URLs are assumed to be Streamable HTTP (as of FastMCP 2.3.0).
</Tip>

To customize the host, port, or log level, provide appropriate keyword arguments to the `run()` method. You can also adjust the SSE path (which clients should connect to) and the message POST endpoint (which clients use to send subsequent messages).

<CodeGroup>
```python {8-12} server.py
from fastmcp import FastMCP

mcp = FastMCP()

if __name__ == "__main__":
    mcp.run(
        transport="sse",
        host="127.0.0.1",
        port=4200,
        log_level="debug",
        path="/my-custom-sse-path",
    )
```
```python {7} client.py
import asyncio
from fastmcp import Client
from fastmcp.client.transports import SSETransport

async def example():
    async with Client(
        transport=SSETransport("http://127.0.0.1:4200/my-custom-sse-path")
    ) as client:
        await client.ping()

if __name__ == "__main__":
    asyncio.run(example())
```
</CodeGroup>



## Async Usage

FastMCP provides both synchronous and asynchronous APIs for running your server. The `run()` method seen in previous examples is a synchronous method that internally uses `anyio.run()` to run the asynchronous server. For applications that are already running in an async context, FastMCP provides the `run_async()` method.

```python {10-12}
from fastmcp import FastMCP
import asyncio

mcp = FastMCP(name="MyServer")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

async def main():
    # Use run_async() in async contexts
    await mcp.run_async(transport="http")

if __name__ == "__main__":
    asyncio.run(main())
```

<Warning>
The `run()` method cannot be called from inside an async function because it already creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running.

Always use `run_async()` inside async functions and `run()` in synchronous contexts.
</Warning>

Both `run()` and `run_async()` accept the same transport arguments, so all the examples above apply to both methods.

## Custom Routes

You can also add custom web routes to your FastMCP server, which will be exposed alongside the MCP endpoint. To do so, use the `@custom_route` decorator. Note that this is less flexible than using a full ASGI framework, but can be useful for adding simple endpoints like health checks to your standalone server.

```python
from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import PlainTextResponse

mcp = FastMCP("MyServer")

@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> PlainTextResponse:
    return PlainTextResponse("OK")

if __name__ == "__main__":
    mcp.run()
```

================
File: docs/getting-started/installation.mdx
================
---
title: Installation
icon: arrow-down-to-line
---
## Install FastMCP

We recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install and manage FastMCP.

If you plan to use FastMCP in your project, you can add it as a dependency with:

```bash
uv add fastmcp
```

Alternatively, you can install it directly with `pip` or `uv pip`:
<CodeGroup>
    ```bash uv
    uv pip install fastmcp
    ```

    ```bash pip
    pip install fastmcp
    ```
</CodeGroup>

### Verify Installation

To verify that FastMCP is installed correctly, you can run the following command:

```bash
fastmcp version
```

You should see output like the following:

```bash
$ fastmcp version

FastMCP version:   0.4.2.dev41+ga077727.d20250410
MCP version:                                1.6.0
Python version:                            3.12.2
Platform:            macOS-15.3.1-arm64-arm-64bit
FastMCP root path:            ~/Developer/fastmcp
```
## Upgrading from the Official MCP SDK

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. 


```python {5}
# Before
# from mcp.server.fastmcp import FastMCP

# After
from fastmcp import FastMCP

mcp = FastMCP("My MCP Server")
```

<Warning>
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.
</Warning>

## Versioning and Breaking Changes

While we make every effort not to introduce backwards-incompatible changes to our public APIs and behavior, FastMCP exists in a rapidly evolving MCP landscape. We're committed to bringing the most cutting-edge features to our users, which occasionally necessitates changes to existing functionality.

As a practice, breaking changes will only occur on minor version changes (e.g., 2.3.x to 2.4.0). A minor version change indicates either:
- A significant new feature set that warrants a new minor version
- Introducing breaking changes that may affect behavior on upgrade

For users concerned about stability in production environments, we recommend pinning FastMCP to a specific version in your dependencies.

Whenever possible, FastMCP will issue deprecation warnings when users attempt to use APIs that are either deprecated or destined for future removal. These warnings will be maintained for at least 1 minor version release, and may be maintained longer.

Note that the "public API" includes the public functionality of the `FastMCP` server, core FastMCP components like `Tool`, `Prompt`, `Resource`, and `ResourceTemplate`, and their respective public methods. It does not include private methods, utilities, or objects that are stored as private attributes, as we do not expect users to rely on those implementation details. 

## Installing for Development

If you plan to contribute to FastMCP, you should begin by cloning the repository and using uv to install all dependencies (development dependencies are installed automatically):

```bash
git clone https://github.com/jlowin/fastmcp.git
cd fastmcp
uv sync
```

This will install all dependencies, including ones for development, and create a virtual environment, which you can activate and use as normal.

### Unit Tests

FastMCP has a comprehensive unit test suite, and all PR's must introduce and pass appropriate tests. To run the tests, use pytest:

```bash
pytest
```

### Pre-Commit Hooks

FastMCP uses pre-commit to manage code quality, including formatting, linting, and type-safety. All PRs must pass the pre-commit hooks, which are run as a part of the CI process. To install the pre-commit hooks, run:

```bash
uv run pre-commit install
```

Alternatively, to run pre-commit manually at any time, use:

```bash
pre-commit run --all-files
```

================
File: docs/getting-started/quickstart.mdx
================
---
title: Quickstart
icon: rocket-launch
---

Welcome! This guide will help you quickly set up FastMCP and run your first MCP server.

If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation).

## Creating a FastMCP Server

A FastMCP server is a collection of tools, resources, and other MCP components. To create a server, start by instantiating the `FastMCP` class. 

Create a new file called `my_server.py` and add the following code:

```python my_server.py
from fastmcp import FastMCP

mcp = FastMCP("My MCP Server")
```


That's it! You've created a FastMCP server, albeit a very boring one. Let's add a tool to make it more interesting.


## Adding a Tool

To add a tool that returns a simple greeting, write a function and decorate it with `@mcp.tool` to register it with the server:

```python my_server.py {5-7}
from fastmcp import FastMCP

mcp = FastMCP("My MCP Server")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"
```


## Testing the Server


To test the server, create a FastMCP client and point it at the server object.

```python my_server.py {1-2, 10-17}
import asyncio
from fastmcp import FastMCP, Client

mcp = FastMCP("My MCP Server")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

client = Client(mcp)

async def call_tool(name: str):
    async with client:
        result = await client.call_tool("greet", {"name": name})
        print(result)

asyncio.run(call_tool("Ford"))
```

There are a few things to note here:
- Clients are asynchronous, so we need to use `asyncio.run` to run the client.
- We must enter a client context (`async with client:`) before using the client. You can make multiple client calls within the same context.

## Running the server

In order to run the server with Python, we need to add a `run` statement to the `__main__` block of the server file.

```python my_server.py {9-10}
from fastmcp import FastMCP

mcp = FastMCP("My MCP Server")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()
```

This lets us run the server with `python my_server.py`, using the default `stdio` transport, which is the standard way to expose an MCP server to a client.

<Tip>
Why do we need the `if __name__ == "__main__":` block?

Within the FastMCP ecosystem, this line may be unnecessary. However, including it ensures that your FastMCP server runs for all users and clients in a consistent way and is therefore recommended as best practice.
</Tip>

### Interacting with the Python server

Now that the server can be executed with `python my_server.py`, we can interact with it like any other MCP server. 

In a new file, create a client and point it at the server file:

```python my_client.py
import asyncio
from fastmcp import Client

client = Client("my_server.py")

async def call_tool(name: str):
    async with client:
        result = await client.call_tool("greet", {"name": name})
        print(result)

asyncio.run(call_tool("Ford"))
```



### Using the FastMCP CLI

To have FastMCP run the server for us, we can use the `fastmcp run` command. This will start the server and keep it running until it is stopped. By default, it will use the `stdio` transport, which is a simple text-based protocol for interacting with the server.

```bash
fastmcp run my_server.py:mcp
```

Note that FastMCP *does not* require the `__main__` block in the server file, and will ignore it if it is present. Instead, it looks for the server object provided in the CLI command (here, `mcp`). If no server object is provided, `fastmcp run` will automatically search for servers called "mcp", "app", or "server" in the file.

<Tip>
We pointed our client at the server file, which is recognized as a Python MCP server and executed with `python my_server.py` by default. This executes the `__main__` block of the server file. There are other ways to run the server, which are described in the [server configuration](/servers/fastmcp#running-the-server) guide.
</Tip>

================
File: docs/getting-started/welcome.mdx
================
---
title: "Welcome to FastMCP 2.0!"
sidebarTitle: "Welcome!"
description: The fast, Pythonic way to build MCP servers and clients.
icon: hand-wave
---


The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and more with clean, Pythonic code:

```python {1}
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()
```


## Beyond the Protocol

FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024. 

This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.

FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.

Ready to upgrade or get started? Follow the [installation instructions](/getting-started/installation), which include steps for upgrading from the official MCP SDK.


## What is MCP?

The Model Context Protocol lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:

- Expose data through `Resources` (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
- Provide functionality through `Tools` (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
- Define interaction patterns through `Prompts` (reusable templates for LLM interactions)
- And more!

FastMCP provides a high-level, Pythonic interface for building, managing, and interacting with these servers.

## Why FastMCP?

The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.

FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.

FastMCP aims to be:

🚀 **Fast**: High-level interface means less code and faster development

🍀 **Simple**: Build MCP servers with minimal boilerplate

🐍 **Pythonic**: Feels natural to Python developers

🔍 **Complete**: A comprehensive platform for all MCP use cases, from dev to prod

FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).



## LLM-Friendly Docs

This documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily. 

There are two ways to access the LLM-friendly documentation:
- [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.

In addition, any page can be accessed as markdown by appending `.md` to the URL. For example, this page would become `https://gofastmcp.com/getting-started/welcome.md`, which you can view [here](/getting-started/welcome.md).

Finally, you can copy the contents of any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.

================
File: docs/integrations/anthropic.mdx
================
---
title: Anthropic API + FastMCP
sidebarTitle: Anthropic API
description: Call FastMCP servers from the Anthropic API
icon: message-smile
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"


Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.

<Tip>
Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
</Tip>

## Create a Server

First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.

```python server.py
import random
from fastmcp import FastMCP

mcp = FastMCP(name="Dice Roller")

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)
```

## Deploy the Server

Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.

For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.

Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:

<CodeGroup>
```bash FastMCP server
python server.py
```

```bash ngrok
ngrok http 8000
```
</CodeGroup>

<Warning>
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
</Warning>

## Call the Server

To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):

```bash
pip install anthropic
```

You'll also need to authenticate with Anthropic. You can do this by setting the `ANTHROPIC_API_KEY` environment variable. Consult the Anthropic SDK documentation for more information.

```bash
export ANTHROPIC_API_KEY="your-api-key"
```

Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.**

```python {5, 13-22}
import anthropic
from rich import print

# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1000,
    messages=[{"role": "user", "content": "Roll a few dice!"}],
    mcp_servers=[
        {
            "type": "url",
            "url": f"{url}/mcp/",
            "name": "dice-server",
        }
    ],
    extra_headers={
        "anthropic-beta": "mcp-client-2025-04-04"
    }
)

print(response.content)
```

If you run this code, you'll see something like the following output:

```text
I'll roll some dice for you! Let me use the dice rolling tool.

I rolled 3 dice and got: 4, 2, 6

The results were 4, 2, and 6. Would you like me to roll again or roll a different number of dice?
```


## Authentication

<VersionBadge version="2.6.0" />

The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.

### Server Authentication

The simplest way to add authentication to the server is to use a bearer token scheme. 

For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Bearer Auth](/servers/auth/bearer) documentation. 

We'll start by creating an RSA key pair to sign and verify tokens.

```python
from fastmcp.server.auth.providers.bearer import RSAKeyPair

key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")
```

<Warning>
FastMCP's `RSAKeyPair` utility is for development and testing only.
</Warning> 

Next, we'll create a `BearerAuthProvider` to authenticate the server. 

```python
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider

auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    audience="dice-server",
)

mcp = FastMCP(name="Dice Roller", auth=auth)
```

Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!**

```python server.py [expandable]
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.auth.providers.bearer import RSAKeyPair
import random

key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")

auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    audience="dice-server",
)

mcp = FastMCP(name="Dice Roller", auth=auth)

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
    mcp.run(transport="http", port=8000)
```

### Client Authentication

If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.

```python
Error code: 400 - {
    "type": "error", 
    "error": {
        "type": "invalid_request_error", 
        "message": "MCP server 'dice-server' requires authentication. Please provide an authorization_token.",
    },
}
```

To authenticate the client, you can pass the token using the `authorization_token` parameter in your MCP server configuration:

```python {8, 21}
import anthropic
from rich import print

# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'

# Your access token (replace with your actual token)
access_token = 'your-access-token'

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1000,
    messages=[{"role": "user", "content": "Roll a few dice!"}],
    mcp_servers=[
        {
            "type": "url",
            "url": f"{url}/mcp/",
            "name": "dice-server",
            "authorization_token": access_token
        }
    ],
    extra_headers={
        "anthropic-beta": "mcp-client-2025-04-04"
    }
)

print(response.content)
```

You should now see the dice roll results in the output.

================
File: docs/integrations/chatgpt.mdx
================
---
title: ChatGPT + FastMCP
sidebarTitle: ChatGPT
description: Connect FastMCP servers to ChatGPT Deep Research
icon: message-smile
tag: NEW
---

ChatGPT supports MCP servers through remote HTTP connections, allowing you to extend ChatGPT's capabilities with custom tools and knowledge from your FastMCP servers.

<Note>
MCP integration with ChatGPT is currently limited to **Deep Research** functionality and is not available for general chat. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
</Note>

<Tip>
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Check out their [sample MCP server](https://github.com/openai/mcp-server-sample) which demonstrates FastMCP in action.
</Tip>

## Deep Research

ChatGPT's Deep Research feature requires MCP servers to be internet-accessible HTTP endpoints with **exactly two specific tools**:

- **`search`**: For searching through your resources and returning matching IDs
- **`fetch`**: For retrieving the full content of specific resources by ID

<Warning>
If your server doesn't implement both `search` and `fetch` tools with the correct signatures, ChatGPT will show the error: "This MCP server doesn't implement our specification". Both tools are required.
</Warning>

### Tool Descriptions Matter

Since ChatGPT needs to understand how to use your tools effectively, **write detailed tool descriptions**. The description teaches ChatGPT how to form queries, what parameters to use, and what to expect from your data. Poor descriptions lead to poor search results.

### Create a Server

A Deep Research-compatible server must implement these two required tools:

- **`search(query: str)`** - Takes a query of any kind and returns matching record IDs
- **`fetch(id: str)`** - Takes an ID and returns the record

**Critical**: Write detailed docstrings for both tools. These descriptions teach ChatGPT how to use your tools effectively. Poor descriptions lead to poor search results.

The `search` tool should take a query (of any kind!) and return IDs. The `fetch` tool should take an ID and return the record.

Here's a reference server implementation you can adapt (see also [OpenAI's sample server](https://github.com/openai/mcp-server-sample) for comparison):

```python server.py [expandable]
import json
from pathlib import Path
from dataclasses import dataclass
from fastmcp import FastMCP

@dataclass
class Record:
    id: str
    title: str
    text: str
    metadata: dict

def create_server(
    records_path: Path | str,
    name: str | None = None,
    instructions: str | None = None,
) -> FastMCP:
    """Create a FastMCP server that can search and fetch records from a JSON file."""
    records = json.loads(Path(records_path).read_text())

    RECORDS = [Record(**r) for r in records]
    LOOKUP = {r.id: r for r in RECORDS}

    mcp = FastMCP(name=name or "Deep Research MCP", instructions=instructions)

    @mcp.tool()
    async def search(query: str):
        """
        Simple unranked keyword search across title, text, and metadata.
        Searches for any of the query terms in the record content.
        Returns a list of matching record IDs for ChatGPT to fetch.
        """
        toks = query.lower().split()
        ids = []
        for r in RECORDS:
            record_txt = " ".join(
                [r.title, r.text, " ".join(r.metadata.values())]
            ).lower()
            if any(t in record_txt for t in toks):
                ids.append(r.id)

        return {"ids": ids}

    @mcp.tool()
    async def fetch(id: str):
        """
        Fetch a record by ID.
        Returns the complete record data for ChatGPT to analyze and cite.
        """
        if id not in LOOKUP:
            raise ValueError(f"Unknown record ID: {id}")
        return LOOKUP[id]

    return mcp

if __name__ == "__main__":
    mcp = create_server("path/to/records.json")
    mcp.run(transport="http", port=8000)
```

### Deploy the Server

Your server must be deployed to a public URL in order for ChatGPT to access it.

For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.

Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:

<CodeGroup>
```bash FastMCP server
python server.py
```

```bash ngrok
ngrok http 8000
```
</CodeGroup>

<Warning>
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
</Warning>

### Connect to ChatGPT

Replace `https://your-server-url.com` with the actual URL of your server (such as your ngrok URL).

1. Open ChatGPT and go to **Settings** → **Connectors**
2. Click **Add custom connector**
3. Enter your server details:
   - **Name**: Library Catalog
   - **URL**: Your server URL (e.g., `https://abc123.ngrok.io`)
   - **Description**: A library catalog for searching and retrieving books

#### Test the Connection

1. Start a new chat in ChatGPT
2. Click **Tools** → **Run deep research** 
3. Select your **Library Catalog** connector as a source
4. Ask questions like:
   - "Search for Python programming books"
   - "Find books about AI and machine learning"
   - "Show me books by the Python Software Foundation"

ChatGPT will use your server's search and fetch tools to find relevant information and cite the sources in its response.

### Troubleshooting

#### "This MCP server doesn't implement our specification"


If you get this error, it most likely means that your server doesn't implement the required tools (`search` and `fetch`). To correct it, ensure that your server meets the service requirements.

================
File: docs/integrations/claude-code.mdx
================
---
title: Claude Code + FastMCP
sidebarTitle: Claude Code
description: Connect FastMCP servers to Claude Code
icon: message-smile
tag: NEW
---

Claude Code supports MCP servers through multiple transport methods, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.

<Note>
Claude Code supports both local and remote MCP servers with flexible configuration options. See the [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp) for other transport methods.
</Note>

<Tip>
Claude Code provides built-in MCP management commands to easily add, configure, and authenticate your FastMCP servers.
</Tip>

## Create a Server

You can create FastMCP servers using STDIO transport, remote HTTP servers, or local HTTP servers. This example shows one common approach: running an HTTP server locally for development.

```python server.py
import random
from fastmcp import FastMCP

mcp = FastMCP(name="Dice Roller")

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)
```

## Connect to Claude Code

Start your server and add it to Claude Code:

```bash
# Start your server first
python server.py
```

Then add it to Claude Code:
```bash
claude mcp add dice --transport http http://localhost:8000/mcp/
```

## Using Your Server

Once connected, Claude Code will automatically discover and use your server's tools when relevant:

```
Roll some dice for me
```

Claude will call your `roll_dice` tool and provide the results. If your server provides resources, you can reference them with `@` mentions like `@dice:file://path/to/resource`.

================
File: docs/integrations/claude-desktop.mdx
================
---
title: Claude Desktop + FastMCP
sidebarTitle: Claude Desktop
description: Call FastMCP servers from Claude Desktop
icon: message-smile
---


Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.

<Note>
Remote MCP server support is currently in beta and available for users on Claude Pro, Max, Team, and Enterprise plans (as of June 2025). Most users will still need to use local STDIO connections.
</Note>

<Note>
This guide focuses specifically on using FastMCP servers with Claude Desktop. For general Claude Desktop MCP setup and official examples, see the [official Claude Desktop quickstart guide](https://modelcontextprotocol.io/quickstart/user).
</Note>


## Requirements

Claude Desktop traditionally requires MCP servers to run locally using STDIO transport, where your server communicates with Claude through standard input/output rather than HTTP. However, users on certain plans now have access to remote server support as well.

<Tip>
If you don't have access to remote server support or need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below.
</Tip>

## Create a Server

The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.

```python server.py
import random
from fastmcp import FastMCP

mcp = FastMCP(name="Dice Roller")

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    mcp.run()
```

## Install the Server

### FastMCP CLI

The easiest way to install a FastMCP server in Claude Desktop is using the `fastmcp install` command. This automatically handles the configuration and dependency management.

```bash
fastmcp install server.py
```

The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:

```bash
# These are equivalent if your server object is named 'mcp'
fastmcp install server.py
fastmcp install server.py:mcp

# Use explicit object name if your server has a different name
fastmcp install server.py:my_custom_server
```

After installation, restart Claude Desktop completely. You should see a hammer icon (🔨) in the bottom left of the input box, indicating that MCP tools are available.

#### Dependencies

If your server has dependencies, include them with the `--with` flag:

```bash
fastmcp install server.py --with pandas --with requests
```

Alternatively, you can specify dependencies directly in your server code:

```python server.py
from fastmcp import FastMCP

mcp = FastMCP(
    name="Dice Roller",
    dependencies=["pandas", "requests"]
)
```

#### Environment Variables

<Warning>
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
</Warning>

If your server needs environment variables (like API keys), you must include them:

```bash
fastmcp install server.py --name "Weather Server" \
  --env-var API_KEY=your-api-key \
  --env-var DEBUG=true
```

Or load them from a `.env` file:

```bash
fastmcp install server.py --name "Weather Server" --env-file .env
```
<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
</Warning>


### Manual Configuration

For more control over the configuration, you can manually edit Claude Desktop's configuration file. You can open the configuration file from Claude's developer settings, or find it in the following locations:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server.

```json
{
  "mcpServers": {
    "dice-roller": {
      "command": "python",
      "args": ["path/to/your/server.py"]
    }
  }
}
```

After updating the configuration file, restart Claude Desktop completely. Look for the hammer icon (🔨) to confirm your server is loaded.

#### Dependencies

If your server has dependencies, you can use `uv` or another package manager to set up the environment.


```json
{
  "mcpServers": {
    "dice-roller": {
      "command": "uv",
      "args": [
        "run",
        "--with", "pandas",
        "--with", "requests", 
        "python",
        "path/to/your/server.py"
      ]
    }
  }
}
```

<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
</Warning>

#### Environment Variables

You can also specify environment variables in the configuration:

```json
{
  "mcpServers": {
    "weather-server": {
      "command": "python",
      "args": ["path/to/weather_server.py"],
      "env": {
        "API_KEY": "your-api-key",
        "DEBUG": "true"
      }
    }
  }
}
```
<Warning>
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
</Warning>


## Remote Servers


Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote server support via integrations. For other users, or as an alternative approach, FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop.

Create a proxy server that connects to a remote HTTP server:

```python proxy_server.py
from fastmcp import FastMCP

# Create a proxy to a remote server
proxy = FastMCP.as_proxy(
    "https://example.com/mcp/sse", 
    name="Remote Server Proxy"
)

if __name__ == "__main__":
    proxy.run()  # Runs via STDIO for Claude Desktop
```

### Authentication

For authenticated remote servers, create an authenticated client following the guidance in the [client auth documentation](/clients/auth/bearer) and pass it to the proxy:

```python auth_proxy_server.py {7}
from fastmcp import FastMCP, Client
from fastmcp.client.auth import BearerAuth

# Create authenticated client
client = Client(
    "https://api.example.com/mcp/sse",
    auth=BearerAuth(token="your-access-token")
)

# Create proxy using the authenticated client
proxy = FastMCP.as_proxy(client, name="Authenticated Proxy")

if __name__ == "__main__":
    proxy.run()
```

================
File: docs/integrations/contrib.mdx
================
---
title: "Contrib Modules"
description: "Community-contributed modules extending FastMCP"
icon: "cubes"
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.2.1" />

FastMCP includes a `contrib` package that holds community-contributed modules. These modules extend FastMCP's functionality but aren't officially maintained by the core team.

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/jlowin/fastmcp/tree/main/src/fastmcp/contrib).

## Usage

To use a contrib module, import it from the `fastmcp.contrib` package:

```python
from fastmcp.contrib import my_module
```

## Important Considerations

- **Stability**: Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
- **Compatibility**: Changes to core FastMCP might break modules in `contrib` without explicit warnings in the main changelog.
- **Dependencies**: Contrib modules may have additional dependencies not required by the core library. These dependencies are typically documented in the module's README or separate requirements files.

## Contributing

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
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
5. Submit a pull request

The ideal contrib module:
- Solves a specific use case or integration need
- Follows FastMCP coding standards
- Includes thorough documentation and examples
- Has comprehensive tests
- Specifies any additional dependencies

================
File: docs/integrations/eunomia-authorization.mdx
================
---
title: Eunomia Authorization + FastMCP
sidebarTitle: Eunomia Authorization
description: Add policy-based authorization to your FastMCP servers
icon: layer-group
tag: NEW
---

Add **policy-based authorization** to your FastMCP servers with minimal code changes using Eunomia authorization middleware.

Control which actions MCP clients can perform on your server by restricting how the agent can access resources, tools and prompts by using JSON-based policies, while obtaining a comprehensive audit log of all access attempts and violations.

## Eunomia Authorization Middleware

The middleware intercepts all MCP requests to your server and automatically maps MCP methods to authorization checks.

```mermaid
sequenceDiagram
    participant MCPClient as MCP Client
    participant EunomiaMiddleware as Eunomia Middleware
    participant MCPServer as FastMCP Server
    participant EunomiaServer as Eunomia Server

    MCPClient->>EunomiaMiddleware: MCP Request
    Note over MCPClient, EunomiaMiddleware: Middleware intercepts request to server
    EunomiaMiddleware->>EunomiaServer: Authorization Check
    EunomiaServer->>EunomiaMiddleware: Authorization Decision (allow/deny)
    EunomiaMiddleware-->>MCPClient: MCP Unauthorized Error (if denied)
    EunomiaMiddleware->>MCPServer: MCP Request (if allowed)
    MCPServer-->>MCPClient: MCP Response (if allowed)
```

<Note>
Eunomia is an AI-specific standalone authorization server that handles policy decisions. You must have an Eunomia server running alongside your FastMCP server for the middleware to function.

Run it in the background with Docker:

```bash
docker run -d -p 8000:8000 ttommitt/eunomia-server:latest
```

</Note>

### Create a Server with Authorization

First, install the `eunomia-mcp` package:

```bash
pip install eunomia-mcp
```

Then create a FastMCP server and add the Eunomia middleware with a few lines of code:

```python server.py
from fastmcp import FastMCP
from eunomia_mcp import create_eunomia_middleware

mcp = FastMCP("Secure FastMCP Server 🔒")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

middleware = [create_eunomia_middleware()]
app = mcp.http_app(middleware=middleware)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)
```

### Configure Access Policies

Use the `eunomia-mcp` CLI in your terminal to manage your authorization policies:

```bash
# Create a default policy configuration file
eunomia-mcp init
```

This creates a policy file you can customize to control access to your MCP tools and resources.

```bash
# Once ready, validate your policy
eunomia-mcp validate mcp_policies.json

# And push it to the Eunomia server
eunomia-mcp push mcp_policies.json
```

### Run the Server

Start your FastMCP server normally:

```bash
python server.py
```

The middleware will now intercept all MCP requests and check them against your policies. Requests include agent identification through headers like `X-Agent-ID`, `X-User-ID`, or `Authorization` and an automatic mapping of MCP methods to authorization resources and actions.

<Tip>
  For detailed policy configuration, custom authentication, and advanced
  deployment patterns, visit the [Eunomia MCP Middleware
  repository][eunomia-github].
</Tip>

[eunomia-github]: https://github.com/whataboutyou-ai/eunomia/tree/main/pkgs/extensions/mcp

================
File: docs/integrations/gemini.mdx
================
---
title: Gemini SDK + FastMCP
sidebarTitle: Gemini SDK
description: Call FastMCP servers from the Google Gemini SDK
icon: message-smile
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

Google's Gemini API includes built-in support for MCP servers in their Python and JavaScript SDKs, allowing you to connect directly to MCP servers and use their tools seamlessly with Gemini models.

## Gemini Python SDK

Google's [Gemini Python SDK](https://ai.google.dev/gemini-api/docs) can use FastMCP clients directly.

<Note>
Google's MCP integration is currently experimental and available in the Python and JavaScript SDKs. The API automatically calls MCP tools when needed and can connect to both local and remote MCP servers.
</Note>

<Tip>
Currently, Gemini's MCP support only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to the AI. Other MCP features like resources and prompts are not currently supported.
</Tip>

### Create a Server

First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.

```python server.py
import random
from fastmcp import FastMCP

mcp = FastMCP(name="Dice Roller")

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    mcp.run()
```

### Call the Server


To use the Gemini API with MCP, you'll need to install the Google Generative AI SDK:

```bash
pip install google-genai
```

You'll also need to authenticate with Google. You can do this by setting the `GEMINI_API_KEY` environment variable. Consult the Gemini SDK documentation for more information.

```bash
export GEMINI_API_KEY="your-api-key"
```

Gemini's SDK interacts directly with the MCP client session. To call the server, you'll need to instantiate a FastMCP client, enter its connection context, and pass the client session to the Gemini SDK.

```python {5, 9, 15}
from fastmcp import Client
from google import genai
import asyncio

mcp_client = Client("server.py")
gemini_client = genai.Client()

async def main():    
    async with mcp_client:
        response = await gemini_client.aio.models.generate_content(
            model="gemini-2.0-flash",
            contents="Roll 3 dice!",
            config=genai.types.GenerateContentConfig(
                temperature=0,
                tools=[mcp_client.session],  # Pass the FastMCP client session
            ),
        )
        print(response.text)

if __name__ == "__main__":
    asyncio.run(main())
```

If you run this code, you'll see output like:

```text
Okay, I rolled 3 dice and got a 5, 4, and 1.
```

### Remote & Authenticated Servers

In the above example, we connected to our local server using `stdio` transport. Because we're using a FastMCP client, you can also connect to any local or remote MCP server, using any [transport](/clients/transports) or [auth](/clients/auth) method supported by FastMCP, simply by changing the client configuration.

For example, to connect to a remote, authenticated server, you can use the following client:

```python
from fastmcp import Client
from fastmcp.client.auth import BearerAuth

mcp_client = Client(
    "https://my-server.com/mcp/",
    auth=BearerAuth("<your-token>"),
)
```

The rest of the code remains the same.

================
File: docs/integrations/openai.mdx
================
---
title: OpenAI API + FastMCP
sidebarTitle: OpenAI API
description: Call FastMCP servers from the OpenAI API
icon: message-smile
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"


## Responses API

OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions.

<Note>
The Responses API is a distinct API from OpenAI's Completions API or Assistants API. At this time, only the Responses API supports MCP.
</Note>

<Tip>
Currently, the Responses API only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to the AI agent. Other MCP features like resources and prompts are not currently supported.
</Tip>


### Create a Server

First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.

```python server.py
import random
from fastmcp import FastMCP

mcp = FastMCP(name="Dice Roller")

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)
```

### Deploy the Server

Your server must be deployed to a public URL in order for OpenAI to access it.

For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.

Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:

<CodeGroup>
```bash FastMCP server
python server.py
```

```bash ngrok
ngrok http 8000
```
</CodeGroup>

<Warning>
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
</Warning>

### Call the Server

To use the Responses API, you'll need to install the OpenAI Python SDK (not included with FastMCP):

```bash
pip install openai
```

You'll also need to authenticate with OpenAI. You can do this by setting the `OPENAI_API_KEY` environment variable. Consult the OpenAI SDK documentation for more information.

```bash
export OPENAI_API_KEY="your-api-key"
```

Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment.

```python {4, 11-16}
from openai import OpenAI

# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'

client = OpenAI()

resp = client.responses.create(
    model="gpt-4.1",
    tools=[
        {
            "type": "mcp",
            "server_label": "dice_server",
            "server_url": f"{url}/mcp/",
            "require_approval": "never",
        },
    ],
    input="Roll a few dice!",
)

print(resp.output_text)
```
If you run this code, you'll see something like the following output:

```text
You rolled 3 dice and got the following results: 6, 4, and 2!
```

### Authentication

<VersionBadge version="2.6.0" />

The Responses API can include headers to authenticate the request, which means you don't have to worry about your server being publicly accessible.

#### Server Authentication

The simplest way to add authentication to the server is to use a bearer token scheme. 

For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Bearer Auth](/servers/auth/bearer) documentation. 

We'll start by creating an RSA key pair to sign and verify tokens.

```python
from fastmcp.server.auth.providers.bearer import RSAKeyPair

key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")
```

<Warning>
FastMCP's `RSAKeyPair` utility is for development and testing only.
</Warning> 

Next, we'll create a `BearerAuthProvider` to authenticate the server. 

```python
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider

auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    audience="dice-server",
)

mcp = FastMCP(name="Dice Roller", auth=auth)
```

Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!**

```python server.py [expandable]
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.auth.providers.bearer import RSAKeyPair
import random

key_pair = RSAKeyPair.generate()
access_token = key_pair.create_token(audience="dice-server")

auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    audience="dice-server",
)

mcp = FastMCP(name="Dice Roller", auth=auth)

@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
    """Roll `n_dice` 6-sided dice and return the results."""
    return [random.randint(1, 6) for _ in range(n_dice)]

if __name__ == "__main__":
    print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
    mcp.run(transport="http", port=8000)
```

#### Client Authentication

If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this:

```python
pythonAPIStatusError: Error code: 424 - {
    "error": {
        "message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)",
        "type": "external_connector_error",
        "param": "tools",
        "code": "http_error"
    }
}
```

As expected, the server is rejecting the request because it's not authenticated.

To authenticate the client, you can pass the token in the `Authorization` header with the `Bearer` scheme:


```python {4, 7, 19-21} [expandable]
from openai import OpenAI

# Your server URL (replace with your actual URL)
url = 'https://your-server-url.com'

# Your access token (replace with your actual token)
access_token = 'your-access-token'

client = OpenAI()

resp = client.responses.create(
    model="gpt-4.1",
    tools=[
        {
            "type": "mcp",
            "server_label": "dice_server",
            "server_url": f"{url}/mcp/",
            "require_approval": "never",
            "headers": {
                "Authorization": f"Bearer {access_token}"
            }
        },
    ],
    input="Roll a few dice!",
)

print(resp.output_text)
```

You should now see the dice roll results in the output.

================
File: docs/patterns/cli.mdx
================
---
title: FastMCP CLI
sidebarTitle: CLI
description: Learn how to use the FastMCP command-line interface
icon: terminal
---

import { VersionBadge } from "/snippets/version-badge.mdx"


FastMCP provides a command-line interface (CLI) that makes it easy to run, develop, and install your MCP servers. The CLI is automatically installed when you install FastMCP.

```bash
fastmcp --help
```

## Commands Overview

| Command | Purpose | Dependency Management |
| ------- | ------- | --------------------- |
| `run` | Run a FastMCP server directly | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `dev` | Run a server with the MCP Inspector for testing | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `install` | Install a server in the Claude desktop app | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `inspect` | Generate a JSON report about a FastMCP server | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `version` | Display version information | N/A |

## Command Details

### `run`

Run a FastMCP server directly or proxy a remote server.

```bash
fastmcp run server.py
```

<Tip>
This command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available.
</Tip>

#### Options

| Option | Flag | Description |
| ------ | ---- | ----------- |
| Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `http`, or `sse`) |
| Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) |
| Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) |
| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |


#### Server Specification
<VersionBadge version="2.3.5" />

The server can be specified in three ways:
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. `server.py:custom_name` - imports and uses the specified server object
3. `http://server-url/path` or `https://server-url/path` - connects to a remote server and creates a proxy

<Tip>
When using `fastmcp run` with a local file, it **ignores** the `if __name__ == "__main__"` block entirely. Instead, it finds your server object and calls its `run()` method directly with the transport options you specify. This means you can use `fastmcp run` to override the transport specified in your code.
</Tip>

For example, if your code contains:

```python
# server.py
from fastmcp import FastMCP

mcp = FastMCP("MyServer")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    # This is ignored when using `fastmcp run`!
    mcp.run(transport="stdio")
```

You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:

```bash
fastmcp run server.py --transport http --port 8000
```

**Examples**

```bash
# Run a local server with Streamable HTTP transport on a custom port
fastmcp run server.py --transport http --port 8000

# Connect to a remote server and proxy as a stdio server
fastmcp run https://example.com/mcp-server

# Connect to a remote server with specified log level
fastmcp run https://example.com/mcp-server --log-level DEBUG
```

### `dev`

Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.

```bash
fastmcp dev server.py
```

<Tip> 
This command runs your server in an isolated environment. All dependencies must be explicitly specified using the `--with` and/or `--with-editable` options.
</Tip>

<Warning>
The `dev` command is a shortcut for testing a server over STDIO only. When the Inspector launches, you may need to:
1. Select "STDIO" from the transport dropdown
2. Connect manually

This command does not support HTTP testing. To test a server over Streamable HTTP or SSE:
1. Start your server manually with the appropriate transport using either the command line:
   ```bash
   fastmcp run server.py --transport http
   ```
   or by setting the transport in your code:
   ```bash
   python server.py  # Assuming your __main__ block sets Streamable HTTP transport
   ```
2. Open the MCP Inspector separately and connect to your running server
</Warning>

#### Options

| Option | Flag | Description |
| ------ | ---- | ----------- |
| Editable Package | `--with-editable`, `-e` | Directory containing pyproject.toml to install in editable mode |
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Inspector Version | `--inspector-version` | Version of the MCP Inspector to use |
| UI Port | `--ui-port` | Port for the MCP Inspector UI |
| Server Port | `--server-port` | Port for the MCP Inspector Proxy server |

**Example**

```bash
# Run dev server with editable mode and additional packages
fastmcp dev server.py -e . --with pandas --with matplotlib
```

### `install`

Install a MCP server in the Claude desktop app.

```bash
fastmcp install server.py
```

Note that for security reasons, Claude runs every MCP server in a completely isolated environment. Therefore, all dependencies must be explicitly specified using the `--with` and/or `--with-editable` options (following `uv` conventions) or by attaching them to your server in code via the `dependencies` parameter.

<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
</Warning>

<Warning>
The `install` command currently only sets up servers for STDIO transport. When installed in the Claude desktop app, your server will be run using STDIO regardless of any transport configuration in your code.
</Warning>

#### Server Specification

The `install` command supports the same `file.py:object` notation as the `run` command:

1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. `server.py:custom_name` - imports and uses the specified server object

**Examples**

```bash
# Auto-detects server object (looks for 'mcp', 'server', or 'app')
fastmcp install server.py

# Uses specific server object
fastmcp install server.py:my_server

# With custom name and dependencies
fastmcp install server.py:my_server -n "My Analysis Server" --with pandas
```

### `inspect`

<VersionBadge version="2.9.0" />

Generate a detailed JSON report about a FastMCP server, including information about its tools, prompts, resources, and capabilities.

```bash
fastmcp inspect server.py
```

The command supports the same server specification format as `run` and `install`:

```bash
# Auto-detect server object
fastmcp inspect server.py

# Specify server object
fastmcp inspect server.py:my_server

# Custom output location
fastmcp inspect server.py --output analysis.json
```

### `version`

Display version information about FastMCP and related components.

```bash
fastmcp version
```

================
File: docs/patterns/decorating-methods.mdx
================
---
title: Decorating Methods
sidebarTitle: Decorating Methods
description: Properly use instance methods, class methods, and static methods with FastMCP decorators.
icon: at
---

FastMCP's decorator system is designed to work with functions, but you may see unexpected behavior if you try to decorate an instance or class method. This guide explains the correct approach for using methods with all FastMCP decorators (`@tool`, `@resource`, and `.prompt`).

## Why Are Methods Hard?

When you apply a FastMCP decorator like `@tool`, `@resource`, or `@prompt` to a method, the decorator captures the function at decoration time. For instance methods and class methods, this poses a challenge because:

1. For instance methods: The decorator gets the unbound method before any instance exists
2. For class methods: The decorator gets the function before it's bound to the class

This means directly decorating these methods doesn't work as expected. In practice, the LLM would see parameters like `self` or `cls` that it cannot provide values for.

Additionally, **FastMCP decorators return objects (Tool, Resource, or Prompt instances) rather than the original function**. This means that when you decorate a method directly, the method becomes the returned object and is no longer callable by your code:

<Warning>
**Don't do this!**

The method will no longer be callable from Python, and the tool won't be callable by LLMs.

```python

from fastmcp import FastMCP
mcp = FastMCP()

class MyClass:
    @mcp.tool
    def my_method(self, x: int) -> int:
        return x * 2

obj = MyClass()
obj.my_method(5)  # Fails - my_method is a Tool, not a function
```
</Warning>

This is another important reason to register methods functionally after defining the class.

## Recommended Patterns

### Instance Methods

<Warning>
**Don't do this!**

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    @mcp.tool  # This won't work correctly
    def add(self, x, y):
        return x + y
```
</Warning>
When the decorator is applied this way, it captures the unbound method. When the LLM later tries to use this component, it will see `self` as a required parameter, but it won't know what to provide for it, causing errors or unexpected behavior.

<Check>
**Do this instead**:

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    def add(self, x, y):
        return x + y

# Create an instance first, then register the bound methods
obj = MyClass()
mcp.tool(obj.add)

# Now you can call it without 'self' showing up as a parameter
await mcp._mcp_call_tool('add', {'x': 1, 'y': 2})  # Returns 3
```
</Check>

This approach works because:
1. You first create an instance of the class (`obj`)
2. When you access the method through the instance (`obj.add`), Python creates a bound method where `self` is already set to that instance
3. When you register this bound method, the system sees a callable that only expects the appropriate parameters, not `self`

### Class Methods

The behavior of decorating class methods depends on the order of decorators:

<Warning>
**Don't do this** (decorator order matters):

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    @classmethod
    @mcp.tool  # This won't work but won't raise an error
    def from_string_v1(cls, s):
        return cls(s)
    
    @mcp.tool
    @classmethod  # This will raise a helpful ValueError
    def from_string_v2(cls, s):
        return cls(s)
```
</Warning>

- If `@classmethod` comes first, then `@mcp.tool`: No error is raised, but it won't work correctly
- If `@mcp.tool` comes first, then `@classmethod`: FastMCP will detect this and raise a helpful `ValueError` with guidance

<Check>
**Do this instead**:

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    @classmethod
    def from_string(cls, s):
        return cls(s)

# Register the class method after the class is defined
mcp.tool(MyClass.from_string)
```
</Check>

This works because:
1. The `@classmethod` decorator is applied properly during class definition
2. When you access `MyClass.from_string`, Python provides a special method object that automatically binds the class to the `cls` parameter
3. When registered, only the appropriate parameters are exposed to the LLM, hiding the implementation detail of the `cls` parameter

### Static Methods

Static methods "work" with FastMCP decorators, but this is not recommended because the FastMCP decorator will not return a callable method. Therefore, you should register static methods the same way as other methods.

<Warning>
**This is not recommended, though it will work.**

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    @mcp.tool
    @staticmethod
    def utility(x, y):
        return x + y
```
</Warning>

This works because `@staticmethod` converts the method to a regular function, which the FastMCP decorator can then properly process. However, this is not recommended because the FastMCP decorator will not return a callable staticmethod. Therefore, you should register static methods the same way as other methods.

<Check>
**Prefer this pattern:**

```python
from fastmcp import FastMCP

mcp = FastMCP()

class MyClass:
    @staticmethod
    def utility(x, y):
        return x + y

# This also works
mcp.tool(MyClass.utility)
```
</Check>

## Additional Patterns

### Creating Components at Class Initialization

You can automatically register instance methods when creating an object:

```python
from fastmcp import FastMCP

mcp = FastMCP()

class ComponentProvider:
    def __init__(self, mcp_instance):
        # Register methods
        mcp_instance.tool(self.tool_method)
        mcp_instance.resource("resource://data")(self.resource_method)
    
    def tool_method(self, x):
        return x * 2
    
    def resource_method(self):
        return "Resource data"

# The methods are automatically registered when creating the instance
provider = ComponentProvider(mcp)
```

This pattern is useful when:
- You want to encapsulate registration logic within the class itself
- You have multiple related components that should be registered together
- You want to ensure that methods are always properly registered when creating an instance

The class automatically registers its methods during initialization, ensuring they're properly bound to the instance before registration.

## Summary

The current behavior of FastMCP decorators with methods is:

- **Static methods**: Can be decorated directly and work perfectly with all FastMCP decorators
- **Class methods**: Cannot be decorated directly and will raise a helpful `ValueError` with guidance
- **Instance methods**: Should be registered after creating an instance using the decorator calls

For class and instance methods, you should register them after creating the instance or class to ensure proper method binding. This ensures that the methods are properly bound before being registered.


Understanding these patterns allows you to effectively organize your components into classes while maintaining proper method binding, giving you the benefits of object-oriented design without sacrificing the simplicity of FastMCP's decorator system.

================
File: docs/patterns/http-requests.mdx
================
---
title: HTTP Requests
sidebarTitle: HTTP Requests
description: Accessing and using HTTP requests in FastMCP servers
icon: network-wired
---
import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.2.11" />

## Overview

When running FastMCP as a web server, your MCP tools, resources, and prompts might need to access the underlying HTTP request information, such as headers, client IP, or query parameters.

FastMCP provides a clean way to access HTTP request information through a dependency function.

## Accessing HTTP Requests

The recommended way to access the current HTTP request is through the `get_http_request()` dependency function:

```python {2, 3, 11}
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_request
from starlette.requests import Request

mcp = FastMCP(name="HTTP Request Demo")

@mcp.tool
async def user_agent_info() -> dict:
    """Return information about the user agent."""
    # Get the HTTP request
    request: Request = get_http_request()
    
    # Access request data
    user_agent = request.headers.get("user-agent", "Unknown")
    client_ip = request.client.host if request.client else "Unknown"
    
    return {
        "user_agent": user_agent,
        "client_ip": client_ip,
        "path": request.url.path,
    }
```

This approach works anywhere within a request's execution flow, not just within your MCP function. It's useful when:

1. You need access to HTTP information in helper functions
2. You're calling nested functions that need HTTP request data
3. You're working with middleware or other request processing code

## Accessing HTTP Headers Only

If you only need request headers and want to avoid potential errors, you can use the `get_http_headers()` helper:

```python {2}
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_headers

mcp = FastMCP(name="Headers Demo")

@mcp.tool
async def safe_header_info() -> dict:
    """Safely get header information without raising errors."""
    # Get headers (returns empty dict if no request context)
    headers = get_http_headers()
    
    # Get authorization header
    auth_header = headers.get("authorization", "")
    is_bearer = auth_header.startswith("Bearer ")
    
    return {
        "user_agent": headers.get("user-agent", "Unknown"),
        "content_type": headers.get("content-type", "Unknown"),
        "has_auth": bool(auth_header),
        "auth_type": "Bearer" if is_bearer else "Other" if auth_header else "None",
        "headers_count": len(headers)
    }
```

By default, `get_http_headers()` excludes problematic headers like `host` and `content-length`. To include all headers, use `get_http_headers(include_all=True)`.

## Important Notes

- HTTP requests are only available when FastMCP is running as part of a web application
- Accessing the HTTP request with `get_http_request()` outside of a web request context will raise a `RuntimeError`
- The `get_http_headers()` function **never raises errors** - it returns an empty dict when no request context is available
- The `get_http_request()` function returns a standard [Starlette Request](https://www.starlette.io/requests/) object

================
File: docs/patterns/testing.mdx
================
---
title: Testing MCP Servers
sidebarTitle: Testing
description: Learn how to test your FastMCP servers effectively 
icon: vial
---


Testing your MCP servers thoroughly is essential for ensuring they work correctly when deployed. FastMCP makes this easy through a variety of testing patterns.

## In-Memory Testing

The most efficient way to test an MCP server is to pass your FastMCP server instance directly to a Client. This enables in-memory testing without having to start a separate server process, which is particularly useful because managing an MCP server programmatically can be challenging.

Here is an example of using a `Client` to test a server with pytest:

```python
import pytest
from fastmcp import FastMCP, Client

@pytest.fixture
def mcp_server():
    server = FastMCP("TestServer")
    
@server.tool
    def greet(name: str) -> str:
        return f"Hello, {name}!"
        
    return server

async def test_tool_functionality(mcp_server):
    # Pass the server directly to the Client constructor
    async with Client(mcp_server) as client:
        result = await client.call_tool("greet", {"name": "World"})
        assert result[0].text == "Hello, World!"
```

This pattern creates a direct connection between the client and server, allowing you to test your server's functionality efficiently.

================
File: docs/patterns/tool-transformation.mdx
================
---
title: Tool Transformation
sidebarTitle: Tool Transformation
description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior.
icon: wand-magic-sparkles
tag: NEW
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.8.0" />

Tool transformation allows you to create new, enhanced tools from existing ones. This powerful feature enables you to adapt tools for different contexts, simplify complex interfaces, or add custom logic without duplicating code.

## Why Transform Tools?

Often, an existing tool is *almost* perfect for your use case, but it might have:
- A confusing description (or no description at all).
- Argument names or descriptions that are not intuitive for an LLM (e.g., `q` instead of `query`).
- Unnecessary parameters that you want to hide from the LLM.
- A need for input validation before the original tool is called.
- A need to modify or format the tool's output.

Instead of rewriting the tool from scratch, you can **transform** it to fit your needs.

## Basic Transformation

The primary way to create a transformed tool is with the `Tool.from_tool()` class method. At its simplest, you can use it to change a tool's top-level metadata like its `name`, `description`, or `tags`.

In the following simple example, we take a generic `search` tool and adjust its name and description to help an LLM client better understand its purpose.

```python {13-21}
from fastmcp import FastMCP
from fastmcp.tools import Tool

mcp = FastMCP()

# The original, generic tool
@mcp.tool
def search(query: str, category: str = "all") -> list[dict]:
    """Searches for items in the database."""
    return database.search(query, category)

# Create a more domain-specific version by changing its metadata
product_search_tool = Tool.from_tool(
    search,
    name="find_products",
    description="""
        Search for products in the e-commerce catalog. 
        Use this when customers ask about finding specific items, 
        checking availability, or browsing product categories.
        """,
)

mcp.add_tool(product_search_tool)
```

<Tip>
When you transform a tool, the original tool remains registered on the server. To avoid confusing an LLM with two similar tools, you can disable the original one:

```python
from fastmcp import FastMCP
from fastmcp.tools import Tool

mcp = FastMCP()

# The original, generic tool
@mcp.tool
def search(query: str, category: str = "all") -> list[dict]:
    ...

# Create a more domain-specific version
product_search_tool = Tool.from_tool(search, ...)
mcp.add_tool(product_search_tool)

# Disable the original tool
search.disable()
```
</Tip>

Now, clients see a tool named `find_products` with a clear, domain-specific purpose and relevant tags, even though it still uses the original generic `search` function's logic.

### Parameters

The `Tool.from_tool()` class method is the primary way to create a transformed tool. It takes the following parameters:

- `tool`: The tool to transform. This is the only required argument.
- `name`: An optional name for the new tool.
- `description`: An optional description for the new tool.
- `transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify.
- `transform_fn`: An optional function that will be called instead of the parent tool's logic.
- `output_schema`: Control output schema and structured outputs (see [Output Schema Control](#output-schema-control)).
- `tags`: An optional set of tags for the new tool.
- `annotations`: An optional set of `ToolAnnotations` for the new tool.
- `serializer`: An optional function that will be called to serialize the result of the new tool.

The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method.



## Modifying Arguments

To modify a tool's parameters, provide a dictionary of `ArgTransform` objects to the `transform_args` parameter of `Tool.from_tool()`. Each key is the name of the *original* argument you want to modify.

<Tip>
You only need to provide a `transform_args` entry for arguments you want to modify. All other arguments will be passed through unchanged.
</Tip>

### The ArgTransform Class

To modify an argument, you need to create an `ArgTransform` object. This object has the following parameters:

- `name`: The new name for the argument.
- `description`: The new description for the argument.
- `default`: The new default value for the argument.
- `default_factory`: A function that will be called to generate a default value for the argument. This is useful for arguments that need to be generated for each tool call, such as timestamps or unique IDs.
- `hide`: Whether to hide the argument from the LLM.
- `required`: Whether the argument is required, usually used to make an optional argument be required instead.
- `type`: The new type for the argument.

<Tip>
Certain combinations of parameters are not allowed. For example, you can only use `default_factory` with `hide=True`, because dynamic defaults cannot be represented in a JSON schema for the client. You can only set required=True for arguments that do not declare a default value.
</Tip>


### Descriptions

By far the most common reason to transform a tool, after its own description, is to improve its argument descriptions. A good description is crucial for helping an LLM understand how to use a parameter correctly. This is especially important when wrapping tools from external APIs, whose argument descriptions may be missing or written for developers, not LLMs.

In this example, we add a helpful description to the `user_id` argument:

```python {16-19}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform

mcp = FastMCP()

@mcp.tool
def find_user(user_id: str):
    """Finds a user by their ID."""
    ...

new_tool = Tool.from_tool(
    find_user,
    transform_args={
        "user_id": ArgTransform(
            description=(
                "The unique identifier for the user, "
                "usually in the format 'usr-xxxxxxxx'."
            )
        )
    }
)
```

### Names

At times, you may want to rename an argument to make it more intuitive for an LLM. 

For example, in the following example, we take a generic `q` argument and expand it to `search_query`:

```python {15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform

mcp = FastMCP()

@mcp.tool
def search(q: str):
    """Searches for items in the database."""
    return database.search(q)

new_tool = Tool.from_tool(
    search,
    transform_args={
        "q": ArgTransform(name="search_query")
    }
)
```

### Default Values

You can update the default value for any argument using the `default` parameter. Here, we change the default value of the `y` argument to 10:

```python{15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform

mcp = FastMCP()

@mcp.tool
def add(x: int, y: int) -> int:
    """Adds two numbers."""
    return x + y

new_tool = Tool.from_tool(
    add,
    transform_args={
        "y": ArgTransform(default=10)
    }
)
```

Default values are especially useful in combination with hidden arguments.

### Hiding Arguments

Sometimes a tool requires arguments that shouldn't be exposed to the LLM, such as API keys, configuration flags, or internal IDs. You can hide these parameters using `hide=True`. Note that you can only hide arguments that have a default value (or for which you provide a new default), because the LLM can't provide a value at call time.

<Tip>
To pass a constant value to the parent tool, combine `hide=True` with `default=<value>`.
</Tip>

```python {19-20}
import os
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform

mcp = FastMCP()

@mcp.tool
def send_email(to: str, subject: str, body: str, api_key: str):
    """Sends an email."""
    ...
    
# Create a simplified version that hides the API key
new_tool = Tool.from_tool(
    send_email,
    name="send_notification",
    transform_args={
        "api_key": ArgTransform(
            hide=True, 
            default=os.environ.get("EMAIL_API_KEY"),
        )
    }
)
```
The LLM now only sees the `to`, `subject`, and `body` parameters. The `api_key` is supplied automatically from an environment variable.

For values that must be generated for each tool call (like timestamps or unique IDs), use `default_factory`, which is called with no arguments every time the tool is called. For example,

```python {3-4}
transform_args = {
    'timestamp': ArgTransform(
        hide=True,
        default_factory=lambda: datetime.now(),
    )
}
```

<Warning>
`default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client.
</Warning>

### Required Values

In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required.

```python {3}
transform_args = {
    'user_id': ArgTransform(
        required=True,
    )
}
```

## Modifying Tool Behavior

<Warning>
With great power comes great responsibility. Modifying tool behavior is a very advanced feature.
</Warning>

In addition to changing a tool's schema, advanced users can also modify its behavior. This is useful for adding validation logic, or for post-processing the tool's output.

The `from_tool()` method takes a `transform_fn` parameter, which is an async function that replaces the parent tool's logic and gives you complete control over the tool's execution.

### The Transform Function

The `transform_fn` is an async function that **completely replaces** the parent tool's logic. 

Critically, the transform function's arguments are used to determine the new tool's final schema. Any arguments that are not already present in the parent tool schema OR the `transform_args` will be added to the new tool's schema. Note that when `transform_args` and your function have the same argument name, the `transform_args` metadata will take precedence, if provided.

```python
async def my_custom_logic(user_input: str, max_length: int = 100) -> str:
    # Your custom logic here - this completely replaces the parent tool
    return f"Custom result for: {user_input[:max_length]}"

Tool.from_tool(transform_fn=my_custom_logic)
```

<Tip>
The name / docstring of the `transform_fn` are ignored. Only its arguments are used to determine the final schema.
</Tip>

### Calling the Parent Tool

Most of the time, you don't want to completely replace the parent tool's behavior. Instead, you want to add validation, modify inputs, or post-process outputs while still leveraging the parent tool's core functionality. For this, FastMCP provides the special `forward()` and `forward_raw()` functions.

Both `forward()` and `forward_raw()` are async functions that let you call the parent tool from within your `transform_fn`:

- **`forward()`** (recommended): Automatically handles argument mapping based on your `ArgTransform` configurations. Call it with the transformed argument names.
- **`forward_raw()`**: Bypasses all transformation and calls the parent tool directly with its original argument names. This is rarely needed unless you're doing complex argument manipulation, perhaps without `arg_transforms`.

The most common transformation pattern is to validate (potentially renamed) arguments before calling the parent tool. Here's an example that validates that `x` and `y` are positive before calling the parent tool:
<Tabs>
<Tab title="Using forward()">

In the simplest case, your parent tool and your transform function have the same arguments. You can call `forward()` with the same argument names as the parent tool:

```python {15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward

mcp = FastMCP()

@mcp.tool
def add(x: int, y: int) -> int:
    """Adds two numbers."""
    return x + y

async def ensure_positive(x: int, y: int) -> int:
    if x <= 0 or y <= 0:
        raise ValueError("x and y must be positive")
    return await forward(x=x, y=y)

new_tool = Tool.from_tool(
    add,
    transform_fn=ensure_positive,
)

mcp.add_tool(new_tool)
```
</Tab>
<Tab title="Using forward() with renamed args">

When your transformed tool has different argument names than the parent tool, you can call `forward()` with the renamed arguments and it will automatically map the arguments to the parent tool's arguments:

```python {15, 20-23}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward

mcp = FastMCP()

@mcp.tool
def add(x: int, y: int) -> int:
    """Adds two numbers."""
    return x + y

async def ensure_positive(a: int, b: int) -> int:
    if a <= 0 or b <= 0:
        raise ValueError("a and b must be positive")
    return await forward(a=a, b=b)

new_tool = Tool.from_tool(
    add,
    transform_fn=ensure_positive,
    transform_args={
        "x": ArgTransform(name="a"),
        "y": ArgTransform(name="b"),
    }
)

mcp.add_tool(new_tool)
```
</Tab>
<Tab title="Using forward_raw()">
Finally, you can use `forward_raw()` to bypass all argument mapping and call the parent tool directly with its original argument names.

```python {15, 20-23}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward

mcp = FastMCP()

@mcp.tool
def add(x: int, y: int) -> int:
    """Adds two numbers."""
    return x + y

async def ensure_positive(a: int, b: int) -> int:
    if a <= 0 or b <= 0:
        raise ValueError("a and b must be positive")
    return await forward_raw(x=a, y=b)

new_tool = Tool.from_tool(
    add,
    transform_fn=ensure_positive,
    transform_args={
        "x": ArgTransform(name="a"),
        "y": ArgTransform(name="b"),
    }
)

mcp.add_tool(new_tool)
```
</Tab>
</Tabs>

### Passing Arguments with **kwargs

If your `transform_fn` includes `**kwargs` in its signature, it will receive **all arguments from the parent tool after `ArgTransform` configurations have been applied**. This is powerful for creating flexible validation functions that don't require you to add every argument to the function signature.

In the following example, we wrap a parent tool that accepts two arguments `x` and `y`. These are renamed to `a` and `b` in the transformed tool, and the transform only validates `a`, passing the other argument through as `**kwargs`.

```python {12, 15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward

mcp = FastMCP()

@mcp.tool
def add(x: int, y: int) -> int:
    """Adds two numbers."""
    return x + y

async def ensure_a_positive(a: int, **kwargs) -> int:
    if a <= 0:
        raise ValueError("a must be positive")
    return await forward(a=a, **kwargs)

new_tool = Tool.from_tool(
    add,
    transform_fn=ensure_a_positive,
    transform_args={
        "x": ArgTransform(name="a"),
        "y": ArgTransform(name="b"),
    }
)

mcp.add_tool(new_tool)
```

<Tip>
In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`.
</Tip>

## Output Schema Control

<VersionBadge version="2.10.0" />

Transformed tools inherit output schemas from their parent by default, but you can control this behavior:

**Inherit from Parent (Default)**
```python
Tool.from_tool(parent_tool, name="renamed_tool")
```
The transformed tool automatically uses the parent tool's output schema and structured output behavior.

**Custom Output Schema**
```python
Tool.from_tool(parent_tool, output_schema={
    "type": "object", 
    "properties": {"status": {"type": "string"}}
})
```
Provide your own schema that differs from the parent. The tool must return data matching this schema.

**Remove Output Schema**
```python
Tool.from_tool(parent_tool, output_schema=False)
```
Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured.

**Full Control with Transform Functions**
```python
async def custom_output(**kwargs) -> ToolResult:
    result = await forward(**kwargs)
    return ToolResult(content=[...], structured_content={...})

Tool.from_tool(parent_tool, transform_fn=custom_output)
```
Use a transform function returning `ToolResult` for complete control over both content blocks and structured outputs.

## Common Patterns

Tool transformation is a flexible feature that supports many powerful patterns. Here are a few common use cases to give you ideas.

### Adapting Remote or Generated Tools
This is one of the most common reasons to use tool transformation. Tools from remote servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/servers/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.

### Chaining Transformations
You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool.

### Context-Aware Tool Factories
You can write functions that act as "factories," generating specialized versions of a tool for different contexts. For example, you could create a `get_my_data` tool that is specific to the currently logged-in user by hiding the `user_id` parameter and providing it automatically.

================
File: docs/python-sdk/fastmcp-cli-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.cli`


FastMCP CLI package.

================
File: docs/python-sdk/fastmcp-cli-claude.mdx
================
---
title: claude
sidebarTitle: claude
---

# `fastmcp.cli.claude`


Claude app integration utilities.

## Functions

### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L14"><Icon icon="github" size="14" /></a></sup>

```python
get_claude_config_path() -> Path | None
```


Get the Claude config directory based on platform.


### `update_claude_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L32"><Icon icon="github" size="14" /></a></sup>

```python
update_claude_config(file_spec: str, server_name: str) -> bool
```


Add or update a FastMCP server in Claude's configuration.

**Args:**
- `file_spec`: Path to the server file, optionally with \:object suffix
- `server_name`: Name for the server in Claude's config
- `with_editable`: Optional directory to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables. These are merged with
any existing variables, with new values taking precedence.

**Raises:**
- `RuntimeError`: If Claude Desktop's config directory is not found, indicating
Claude Desktop may not be installed or properly set up.

================
File: docs/python-sdk/fastmcp-cli-cli.mdx
================
---
title: cli
sidebarTitle: cli
---

# `fastmcp.cli.cli`


FastMCP CLI tools.

## Functions

### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L87"><Icon icon="github" size="14" /></a></sup>

```python
version(ctx: Context)
```

### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L110"><Icon icon="github" size="14" /></a></sup>

```python
dev(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], inspector_version: Annotated[str | None, typer.Option('--inspector-version', help='Version of the MCP Inspector to use')] = None, ui_port: Annotated[int | None, typer.Option('--ui-port', help='Port for the MCP Inspector UI')] = None, server_port: Annotated[int | None, typer.Option('--server-port', help='Port for the MCP Inspector Proxy server')] = None) -> None
```


Run a MCP server with the MCP Inspector.


### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L227"><Icon icon="github" size="14" /></a></sup>

```python
run(ctx: typer.Context, server_spec: str = typer.Argument(..., help='Python file, object specification (file:obj), or URL'), transport: Annotated[str | None, typer.Option('--transport', '-t', help='Transport protocol to use (stdio, http, or sse)')] = None, host: Annotated[str | None, typer.Option('--host', help='Host to bind to when using http transport (default: 127.0.0.1)')] = None, port: Annotated[int | None, typer.Option('--port', '-p', help='Port to bind to when using http transport (default: 8000)')] = None, log_level: Annotated[str | None, typer.Option('--log-level', '-l', help='Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')] = None) -> None
```


Run a MCP server or connect to a remote one.

The server can be specified in three ways:
1. Module approach: server.py - runs the module directly, looking for an object named mcp/server/app.

2. Import approach: server.py:app - imports and runs the specified server object.

3. URL approach: http://server-url - connects to a remote server and creates a proxy.



Note: This command runs the server directly. You are responsible for ensuring
all dependencies are available.

Server arguments can be passed after -- :
fastmcp run server.py -- --config config.json --debug


### `install` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L313"><Icon icon="github" size="14" /></a></sup>

```python
install(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), server_name: Annotated[str | None, typer.Option('--name', '-n', help="Custom name for the server (defaults to server's name attribute or file name)")] = None, with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], env_vars: Annotated[list[str], typer.Option('--env-var', '-v', help='Environment variables in KEY=VALUE format')] = [], env_file: Annotated[Path | None, typer.Option('--env-file', '-f', help='Load environment variables from a .env file', exists=True, file_okay=True, dir_okay=False, resolve_path=True)] = None) -> None
```


Install a MCP server in the Claude desktop app.

Environment variables are preserved once added and only updated if new values
are explicitly provided.


### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L444"><Icon icon="github" size="14" /></a></sup>

```python
inspect(server_spec: str = typer.Argument(..., help='Python file to inspect, optionally with :object suffix'), output: Annotated[Path, typer.Option('--output', '-o', help='Output file path for the JSON report (default: server-info.json)')] = Path('server-info.json')) -> None
```


Inspect a FastMCP server and generate a JSON report.

This command analyzes a FastMCP server (v1.x or v2.x) and generates
a comprehensive JSON report containing information about the server's
name, instructions, version, tools, prompts, resources, templates,
and capabilities.

**Examples:**

fastmcp inspect server.py
fastmcp inspect server.py -o report.json
fastmcp inspect server.py:mcp -o analysis.json
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json

================
File: docs/python-sdk/fastmcp-cli-run.mdx
================
---
title: run
sidebarTitle: run
---

# `fastmcp.cli.run`


FastMCP run command implementation.

## Functions

### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L14"><Icon icon="github" size="14" /></a></sup>

```python
is_url(path: str) -> bool
```


Check if a string is a URL.


### `parse_file_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L20"><Icon icon="github" size="14" /></a></sup>

```python
parse_file_path(server_spec: str) -> tuple[Path, str | None]
```


Parse a file path that may include a server object specification.

**Args:**
- `server_spec`: Path to file, optionally with \:object suffix

**Returns:**
- Tuple of (file_path, server_object)


### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L51"><Icon icon="github" size="14" /></a></sup>

```python
import_server(file: Path, server_object: str | None = None) -> Any
```


Import a MCP server from a file.

**Args:**
- `file`: Path to the file
- `server_object`: Optional object name in format "module\:object" or just "object"

**Returns:**
- The server object


### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L121"><Icon icon="github" size="14" /></a></sup>

```python
create_client_server(url: str) -> Any
```


Create a FastMCP server from a client URL.

**Args:**
- `url`: The URL to connect to

**Returns:**
- A FastMCP server instance


### `import_server_with_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L141"><Icon icon="github" size="14" /></a></sup>

```python
import_server_with_args(file: Path, server_object: str | None = None, server_args: list[str] | None = None) -> Any
```


Import a server with optional command line arguments.

**Args:**
- `file`: Path to the server file
- `server_object`: Optional server object name
- `server_args`: Optional command line arguments to inject

**Returns:**
- The imported server object


### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L165"><Icon icon="github" size="14" /></a></sup>

```python
run_command(server_spec: str, transport: str | None = None, host: str | None = None, port: int | None = None, log_level: str | None = None, server_args: list[str] | None = None) -> None
```


Run a MCP server or connect to a remote one.

**Args:**
- `server_spec`: Python file, object specification (file\:obj), or URL
- `transport`: Transport protocol to use
- `host`: Host to bind to when using http transport
- `port`: Port to bind to when using http transport
- `log_level`: Log level
- `server_args`: Additional arguments to pass to the server

================
File: docs/python-sdk/fastmcp-client-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.client`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-client-auth-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.client.auth`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-client-auth-bearer.mdx
================
---
title: bearer
sidebarTitle: bearer
---

# `fastmcp.client.auth.bearer`

## Classes

### `BearerAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/bearer.py#L11"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/bearer.py#L15"><Icon icon="github" size="14" /></a></sup>

```python
auth_flow(self, request)
```

================
File: docs/python-sdk/fastmcp-client-auth-oauth.mdx
================
---
title: oauth
sidebarTitle: oauth
---

# `fastmcp.client.auth.oauth`

## Functions

### `default_cache_dir` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L38"><Icon icon="github" size="14" /></a></sup>

```python
default_cache_dir() -> Path
```

### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L295"><Icon icon="github" size="14" /></a></sup>

```python
OAuth(mcp_url: str, scopes: str | list[str] | None = None, client_name: str = 'FastMCP Client', token_storage_cache_dir: Path | None = None, additional_client_metadata: dict[str, Any] | None = None) -> _MCPOAuthClientProvider
```


Create an OAuthClientProvider for an MCP server.

This is intended to be provided to the `auth` parameter of an
httpx.AsyncClient (or appropriate FastMCP client/transport instance)

**Args:**
- `mcp_url`: Full URL to the MCP endpoint (e.g. "http\://host/mcp/sse/")
- `scopes`: OAuth scopes to request. Can be a
- `client_name`: Name for this client during registration
- `token_storage_cache_dir`: Directory for FileTokenStorage
- `additional_client_metadata`: Extra fields for OAuthClientMetadata

**Returns:**
- OAuthClientProvider


## Classes

### `ServerOAuthMetadata` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L43"><Icon icon="github" size="14" /></a></sup>


More flexible OAuth metadata model that accepts broader ranges of values
than the restrictive MCP standard model.

This handles real-world OAuth servers like PayPal that may support
additional methods not in the MCP specification.


### `OAuthClientProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L68"><Icon icon="github" size="14" /></a></sup>


OAuth client provider with more flexible OAuth metadata discovery.


### `FileTokenStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L116"><Icon icon="github" size="14" /></a></sup>


File-based token storage implementation for OAuth credentials and tokens.
Implements the mcp.client.auth.TokenStorage protocol.

Each instance is tied to a specific server URL for proper token isolation.


**Methods:**

#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L131"><Icon icon="github" size="14" /></a></sup>

```python
get_base_url(url: str) -> str
```

Extract the base URL (scheme + host) from a URL.


#### `get_cache_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L136"><Icon icon="github" size="14" /></a></sup>

```python
get_cache_key(self) -> str
```

Generate a safe filesystem key from the server's base URL.


#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L208"><Icon icon="github" size="14" /></a></sup>

```python
clear(self) -> None
```

Clear all cached data for this server.


#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L217"><Icon icon="github" size="14" /></a></sup>

```python
clear_all(cls, cache_dir: Path | None = None) -> None
```

Clear all cached data for all servers.

================
File: docs/python-sdk/fastmcp-client-client.mdx
================
---
title: client
sidebarTitle: client
---

# `fastmcp.client.client`

## Classes

### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L60"><Icon icon="github" size="14" /></a></sup>


MCP client that delegates connection management to a Transport instance.

The Client class is responsible for MCP protocol logic, while the Transport
handles connection establishment and management. Client provides methods for
working with resources, prompts, tools and other MCP capabilities.

**Args:**
- `transport`: Connection source specification, which can be\:
- ClientTransport\: Direct transport instance
- FastMCP\: In-process FastMCP server
- AnyUrl | str\: URL to connect to
- Path\: File path for local socket
- MCPConfig\: MCP server configuration
- dict\: Transport configuration
- `roots`: Optional RootsList or RootsHandler for filesystem access
- `sampling_handler`: Optional handler for sampling requests
- `log_handler`: Optional handler for log messages
- `message_handler`: Optional handler for protocol messages
- `progress_handler`: Optional handler for progress notifications
- `timeout`: Optional timeout for requests (seconds or timedelta)
- `init_timeout`: Optional timeout for initial connection (seconds or timedelta).
Set to 0 to disable. If None, uses the value in the FastMCP global settings.

**Examples:**

```python # Connect to FastMCP server client =
Client("http://localhost:8080")

async with client:
    # List available resources resources = await client.list_resources()

    # Call a tool result = await client.call_tool("my_tool", {"param":
    "value"})
```


**Methods:**

#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L207"><Icon icon="github" size="14" /></a></sup>

```python
session(self) -> ClientSession
```

Get the current active session. Raises RuntimeError if not connected.


#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L217"><Icon icon="github" size="14" /></a></sup>

```python
initialize_result(self) -> mcp.types.InitializeResult
```

Get the result of the initialization request.


#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L225"><Icon icon="github" size="14" /></a></sup>

```python
set_roots(self, roots: RootsList | RootsHandler) -> None
```

Set the roots for the client. This does not automatically call `send_roots_list_changed`.


#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L229"><Icon icon="github" size="14" /></a></sup>

```python
set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
```

Set the sampling callback for the client.


#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L235"><Icon icon="github" size="14" /></a></sup>

```python
is_connected(self) -> bool
```

Check if the client is currently connected.

================
File: docs/python-sdk/fastmcp-client-logging.mdx
================
---
title: logging
sidebarTitle: logging
---

# `fastmcp.client.logging`

## Functions

### `create_log_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/logging.py#L20"><Icon icon="github" size="14" /></a></sup>

```python
create_log_callback(handler: LogHandler | None = None) -> LoggingFnT
```

================
File: docs/python-sdk/fastmcp-client-oauth_callback.mdx
================
---
title: oauth_callback
sidebarTitle: oauth_callback
---

# `fastmcp.client.oauth_callback`



OAuth callback server for handling authorization code flows.

This module provides a reusable callback server that can handle OAuth redirects
and display styled responses to users.


## Functions

### `create_callback_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L25"><Icon icon="github" size="14" /></a></sup>

```python
create_callback_html(message: str, is_success: bool = True, title: str = 'FastMCP OAuth', server_url: str | None = None) -> str
```


Create a styled HTML response for OAuth callbacks.


### `create_oauth_callback_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L197"><Icon icon="github" size="14" /></a></sup>

```python
create_oauth_callback_server(port: int, callback_path: str = '/callback', server_url: str | None = None, response_future: asyncio.Future | None = None) -> Server
```


Create an OAuth callback server.

**Args:**
- `port`: The port to run the server on
- `callback_path`: The path to listen for OAuth redirects on
- `server_url`: Optional server URL to display in success messages
- `response_future`: Optional future to resolve when OAuth callback is received

**Returns:**
- Configured uvicorn Server instance (not yet running)


## Classes

### `CallbackResponse` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L183"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L190"><Icon icon="github" size="14" /></a></sup>

```python
from_dict(cls, data: dict[str, str]) -> CallbackResponse
```

#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L193"><Icon icon="github" size="14" /></a></sup>

```python
to_dict(self) -> dict[str, str]
```

================
File: docs/python-sdk/fastmcp-client-progress.mdx
================
---
title: progress
sidebarTitle: progress
---

# `fastmcp.client.progress`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-client-roots.mdx
================
---
title: roots
sidebarTitle: roots
---

# `fastmcp.client.roots`

## Functions

### `convert_roots_list` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/roots.py#L19"><Icon icon="github" size="14" /></a></sup>

```python
convert_roots_list(roots: RootsList) -> list[mcp.types.Root]
```

### `create_roots_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/roots.py#L33"><Icon icon="github" size="14" /></a></sup>

```python
create_roots_callback(handler: RootsList | RootsHandler) -> ListRootsFnT
```

================
File: docs/python-sdk/fastmcp-client-sampling.mdx
================
---
title: sampling
sidebarTitle: sampling
---

# `fastmcp.client.sampling`

## Functions

### `create_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/sampling.py#L25"><Icon icon="github" size="14" /></a></sup>

```python
create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT
```

================
File: docs/python-sdk/fastmcp-client-transports.mdx
================
---
title: transports
sidebarTitle: transports
---

# `fastmcp.client.transports`

## Functions

### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L837"><Icon icon="github" size="14" /></a></sup>

```python
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
```


Infer the appropriate transport type from the given transport argument.

This function attempts to infer the correct transport type from the provided
argument, handling various input types and converting them to the appropriate
ClientTransport subclass.

The function supports these input types:
- ClientTransport: Used directly without modification
- FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers

For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.

For MCPConfig with multiple servers, a composite client is created where each server
is mounted with its name as prefix. This allows accessing tools and resources from multiple
servers through a single unified client interface, using naming patterns like
`servername_toolname` for tools and `protocol://servername/path` for resources.
If the MCPConfig contains only one server, a direct connection is established without prefixing.

**Examples:**

```python
# Connect to a local Python script
transport = infer_transport("my_script.py")

# Connect to a remote server via HTTP
transport = infer_transport("http://example.com/mcp")

# Connect to multiple servers using MCPConfig
config = {
    "mcpServers": {
        "weather": {"url": "http://weather.example.com/mcp"},
        "calendar": {"url": "http://calendar.example.com/mcp"}
    }
}
transport = infer_transport(config)
```


## Classes

### `SessionKwargs` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L52"><Icon icon="github" size="14" /></a></sup>


Keyword arguments for the MCP ClientSession constructor.


### `ClientTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L63"><Icon icon="github" size="14" /></a></sup>


Abstract base class for different MCP client transport mechanisms.

A Transport is responsible for establishing and managing connections
to an MCP server, and providing a ClientSession within an async context.


### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L109"><Icon icon="github" size="14" /></a></sup>


Transport implementation that connects to an MCP server via WebSockets.


### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L148"><Icon icon="github" size="14" /></a></sup>


Transport implementation that connects to an MCP server via Server-Sent Events.


### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L223"><Icon icon="github" size="14" /></a></sup>


Transport implementation that connects to an MCP server via Streamable HTTP Requests.


### `StdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L299"><Icon icon="github" size="14" /></a></sup>


Base transport for connecting to an MCP server via subprocess with stdio.

This is a base class that can be subclassed for specific command-based
transports like Python, Node, Uvx, etc.


### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L416"><Icon icon="github" size="14" /></a></sup>


Transport for running Python scripts.


### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L462"><Icon icon="github" size="14" /></a></sup>


Transport for running FastMCP servers using the FastMCP CLI.


### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L489"><Icon icon="github" size="14" /></a></sup>


Transport for running Node.js scripts.


### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L531"><Icon icon="github" size="14" /></a></sup>


Transport for running commands via the uvx tool.


### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L597"><Icon icon="github" size="14" /></a></sup>


Transport for running commands via the npx tool.


### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L659"><Icon icon="github" size="14" /></a></sup>


In-memory transport for FastMCP servers.

This transport connects directly to a FastMCP server instance in the same
Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
servers from the low-level MCP SDK. This is particularly useful for unit
tests or scenarios where client and server run in the same runtime.


### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L713"><Icon icon="github" size="14" /></a></sup>


Transport for connecting to one or more MCP servers defined in an MCPConfig.

This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
object or dictionary matching the MCPConfig schema. It supports two key scenarios:

1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
   all servers on a single FastMCP instance, with each server's name used as its mounting prefix.

In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
and resources with the pattern `protocol://{server_name}/path/to/resource`.

This is particularly useful for creating clients that need to interact with multiple specialized
MCP servers through a single interface, simplifying client code.

**Examples:**

```python
from fastmcp import Client
from fastmcp.utilities.mcp_config import MCPConfig

# Create a config with multiple servers
config = {
    "mcpServers": {
        "weather": {
            "url": "https://weather-api.example.com/mcp",
            "transport": "http"
        },
        "calendar": {
            "url": "https://calendar-api.example.com/mcp",
            "transport": "http"
        }
    }
}

# Create a client with the config
client = Client(config)

async with client:
    # Access tools with prefixes
    weather = await client.call_tool("weather_get_forecast", {"city": "London"})
    events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})

    # Access resources with prefixed URIs
    icons = await client.read_resource("weather://weather/icons/sunny")
```

================
File: docs/python-sdk/fastmcp-exceptions.mdx
================
---
title: exceptions
sidebarTitle: exceptions
---

# `fastmcp.exceptions`


Custom exceptions for FastMCP.

## Classes

### `FastMCPError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L6"><Icon icon="github" size="14" /></a></sup>


Base error for FastMCP.


### `ValidationError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L10"><Icon icon="github" size="14" /></a></sup>


Error in validating parameters or return values.


### `ResourceError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L14"><Icon icon="github" size="14" /></a></sup>


Error in resource operations.


### `ToolError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L18"><Icon icon="github" size="14" /></a></sup>


Error in tool operations.


### `PromptError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L22"><Icon icon="github" size="14" /></a></sup>


Error in prompt operations.


### `InvalidSignature` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L26"><Icon icon="github" size="14" /></a></sup>


Invalid signature for use with FastMCP.


### `ClientError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L30"><Icon icon="github" size="14" /></a></sup>


Error in client operations.


### `NotFoundError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L34"><Icon icon="github" size="14" /></a></sup>


Object not found.


### `DisabledError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L38"><Icon icon="github" size="14" /></a></sup>


Object is disabled.

================
File: docs/python-sdk/fastmcp-prompts-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.prompts`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-prompts-prompt_manager.mdx
================
---
title: prompt_manager
sidebarTitle: prompt_manager
---

# `fastmcp.prompts.prompt_manager`

## Classes

### `PromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L21"><Icon icon="github" size="14" /></a></sup>


Manages FastMCP prompts.


**Methods:**

#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L45"><Icon icon="github" size="14" /></a></sup>

```python
mount(self, server: MountedServer) -> None
```

Adds a mounted server as a source for prompts.


#### `add_prompt_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L114"><Icon icon="github" size="14" /></a></sup>

```python
add_prompt_from_fn(self, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None) -> FunctionPrompt
```

Create a prompt from a function.


#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L134"><Icon icon="github" size="14" /></a></sup>

```python
add_prompt(self, prompt: Prompt) -> Prompt
```

Add a prompt to the manager.

================
File: docs/python-sdk/fastmcp-prompts-prompt.mdx
================
---
title: prompt
sidebarTitle: prompt
---

# `fastmcp.prompts.prompt`


Base classes for FastMCP prompts.

## Functions

### `Message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L32"><Icon icon="github" size="14" /></a></sup>

```python
Message(content: str | MCPContent, role: Role | None = None, **kwargs: Any) -> PromptMessage
```


A user-friendly constructor for PromptMessage.


## Classes

### `PromptArgument` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L54"><Icon icon="github" size="14" /></a></sup>


An argument that can be passed to a prompt.


### `Prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L66"><Icon icon="github" size="14" /></a></sup>


A prompt template that can be rendered with parameters.


**Methods:**

#### `to_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L73"><Icon icon="github" size="14" /></a></sup>

```python
to_mcp_prompt(self, **overrides: Any) -> MCPPrompt
```

Convert the prompt to an MCP prompt.


#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L91"><Icon icon="github" size="14" /></a></sup>

```python
from_function(fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
```

Create a Prompt from a function.

The function can return:
- A string (converted to a message)
- A Message object
- A dict (converted to a message)
- A sequence of any of the above


### `FunctionPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L119"><Icon icon="github" size="14" /></a></sup>


A prompt that is a function.


**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L125"><Icon icon="github" size="14" /></a></sup>

```python
from_function(cls, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
```

Create a Prompt from a function.

The function can return:
- A string (converted to a message)
- A Message object
- A dict (converted to a message)
- A sequence of any of the above

================
File: docs/python-sdk/fastmcp-resources-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.resources`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-resources-resource_manager.mdx
================
---
title: resource_manager
sidebarTitle: resource_manager
---

# `fastmcp.resources.resource_manager`


Resource manager functionality.

## Classes

### `ResourceManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L28"><Icon icon="github" size="14" /></a></sup>


Manages FastMCP resources.


**Methods:**

#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L60"><Icon icon="github" size="14" /></a></sup>

```python
mount(self, server: MountedServer) -> None
```

Adds a mounted server as a source for resources and templates.


#### `add_resource_or_template_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L182"><Icon icon="github" size="14" /></a></sup>

```python
add_resource_or_template_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource | ResourceTemplate
```

Add a resource or template to the manager from a function.

**Args:**
- `fn`: The function to register as a resource or template
- `uri`: The URI for the resource or template
- `name`: Optional name for the resource or template
- `description`: Optional description of the resource or template
- `mime_type`: Optional MIME type for the resource or template
- `tags`: Optional set of tags for categorizing the resource or template

**Returns:**
- The added resource or template. If a resource or template with the same URI already exists,
- returns the existing resource or template.


#### `add_resource_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L230"><Icon icon="github" size="14" /></a></sup>

```python
add_resource_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource
```

Add a resource to the manager from a function.

**Args:**
- `fn`: The function to register as a resource
- `uri`: The URI for the resource
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource

**Returns:**
- The added resource. If a resource with the same URI already exists,
- returns the existing resource.


#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L270"><Icon icon="github" size="14" /></a></sup>

```python
add_resource(self, resource: Resource) -> Resource
```

Add a resource to the manager.

**Args:**
- `resource`: A Resource instance to add. The resource's .key attribute
will be used as the storage key. To overwrite it, call
Resource.with_key() before calling this method.


#### `add_template_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L292"><Icon icon="github" size="14" /></a></sup>

```python
add_template_from_fn(self, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> ResourceTemplate
```

Create a template from a function.


#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L319"><Icon icon="github" size="14" /></a></sup>

```python
add_template(self, template: ResourceTemplate) -> ResourceTemplate
```

Add a template to the manager.

**Args:**
- `template`: A ResourceTemplate instance to add. The template's .key attribute
will be used as the storage key. To overwrite it, call
ResourceTemplate.with_key() before calling this method.

**Returns:**
- The added template. If a template with the same URI already exists,
- returns the existing template.

================
File: docs/python-sdk/fastmcp-resources-resource.mdx
================
---
title: resource
sidebarTitle: resource
---

# `fastmcp.resources.resource`


Base classes and interfaces for FastMCP resources.

## Classes

### `Resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L32"><Icon icon="github" size="14" /></a></sup>


Base class for all resources.


**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L48"><Icon icon="github" size="14" /></a></sup>

```python
from_function(fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
```

#### `set_default_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L69"><Icon icon="github" size="14" /></a></sup>

```python
set_default_mime_type(cls, mime_type: str | None) -> str
```

Set default MIME type if not provided.


#### `set_default_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L76"><Icon icon="github" size="14" /></a></sup>

```python
set_default_name(self) -> Self
```

Set default name from URI if not provided.


#### `to_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L91"><Icon icon="github" size="14" /></a></sup>

```python
to_mcp_resource(self, **overrides: Any) -> MCPResource
```

Convert the resource to an MCPResource.


#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L105"><Icon icon="github" size="14" /></a></sup>

```python
key(self) -> str
```

The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.


### `FunctionResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L115"><Icon icon="github" size="14" /></a></sup>


A resource that defers data loading by wrapping a function.

The function is only called when the resource is read, allowing for lazy loading
of potentially expensive data. This is particularly useful when listing resources,
as the function won't be called until the resource is actually accessed.

The function can return:
- str for text content (default)
- bytes for binary content
- other types will be converted to JSON


**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L131"><Icon icon="github" size="14" /></a></sup>

```python
from_function(cls, fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
```

Create a FunctionResource from a function.

================
File: docs/python-sdk/fastmcp-resources-template.mdx
================
---
title: template
sidebarTitle: template
---

# `fastmcp.resources.template`


Resource template functionality.

## Functions

### `build_regex` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L28"><Icon icon="github" size="14" /></a></sup>

```python
build_regex(template: str) -> re.Pattern
```

### `match_uri_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L44"><Icon icon="github" size="14" /></a></sup>

```python
match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
```

## Classes

### `ResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L52"><Icon icon="github" size="14" /></a></sup>


A template for dynamically creating resources.


**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L69"><Icon icon="github" size="14" /></a></sup>

```python
from_function(fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
```

#### `set_default_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L90"><Icon icon="github" size="14" /></a></sup>

```python
set_default_mime_type(cls, mime_type: str | None) -> str
```

Set default MIME type if not provided.


#### `matches` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L96"><Icon icon="github" size="14" /></a></sup>

```python
matches(self, uri: str) -> dict[str, Any] | None
```

Check if URI matches template and extract parameters.


#### `to_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L124"><Icon icon="github" size="14" /></a></sup>

```python
to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate
```

Convert the resource template to an MCPResourceTemplate.


#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L135"><Icon icon="github" size="14" /></a></sup>

```python
from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate
```

Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object.


#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L148"><Icon icon="github" size="14" /></a></sup>

```python
key(self) -> str
```

The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.


### `FunctionResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L158"><Icon icon="github" size="14" /></a></sup>


A template for dynamically creating resources.


**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L179"><Icon icon="github" size="14" /></a></sup>

```python
from_function(cls, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
```

Create a template from a function.

================
File: docs/python-sdk/fastmcp-resources-types.mdx
================
---
title: types
sidebarTitle: types
---

# `fastmcp.resources.types`


Concrete resource implementations.

## Classes

### `TextResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L21"><Icon icon="github" size="14" /></a></sup>


A resource that reads from a string.


### `BinaryResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L31"><Icon icon="github" size="14" /></a></sup>


A resource that reads from bytes.


### `FileResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L41"><Icon icon="github" size="14" /></a></sup>


A resource that reads from a file.

Set is_binary=True to read file as binary data instead of text.


**Methods:**

#### `validate_absolute_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L59"><Icon icon="github" size="14" /></a></sup>

```python
validate_absolute_path(cls, path: Path) -> Path
```

Ensure path is absolute.


#### `set_binary_from_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L67"><Icon icon="github" size="14" /></a></sup>

```python
set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool
```

Set is_binary based on mime_type if not explicitly set.


### `HttpResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L84"><Icon icon="github" size="14" /></a></sup>


A resource that reads from an HTTP endpoint.


### `DirectoryResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L100"><Icon icon="github" size="14" /></a></sup>


A resource that lists files in a directory.


**Methods:**

#### `validate_absolute_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L116"><Icon icon="github" size="14" /></a></sup>

```python
validate_absolute_path(cls, path: Path) -> Path
```

Ensure path is absolute.


#### `list_files` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L122"><Icon icon="github" size="14" /></a></sup>

```python
list_files(self) -> list[Path]
```

List files in the directory.

================
File: docs/python-sdk/fastmcp-server-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.server`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-server-auth-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.server.auth`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-server-auth-auth.mdx
================
---
title: auth
sidebarTitle: auth
---

# `fastmcp.server.auth.auth`

## Classes

### `OAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L14"><Icon icon="github" size="14" /></a></sup>

================
File: docs/python-sdk/fastmcp-server-auth-providers-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.server.auth.providers`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-server-auth-providers-bearer_env.mdx
================
---
title: bearer_env
sidebarTitle: bearer_env
---

# `fastmcp.server.auth.providers.bearer_env`

## Classes

### `EnvBearerAuthProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer_env.py#L8"><Icon icon="github" size="14" /></a></sup>


Settings for the BearerAuthProvider.


### `EnvBearerAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer_env.py#L24"><Icon icon="github" size="14" /></a></sup>


A BearerAuthProvider that loads settings from environment variables. Any
providing setting will always take precedence over the environment
variables.

================
File: docs/python-sdk/fastmcp-server-auth-providers-bearer.mdx
================
---
title: bearer
sidebarTitle: bearer
---

# `fastmcp.server.auth.providers.bearer`

## Classes

### `JWKData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L29"><Icon icon="github" size="14" /></a></sup>


JSON Web Key data structure.


### `JWKSData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L42"><Icon icon="github" size="14" /></a></sup>


JSON Web Key Set data structure.


### `RSAKeyPair` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L49"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `generate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L54"><Icon icon="github" size="14" /></a></sup>

```python
generate(cls) -> 'RSAKeyPair'
```

Generate an RSA key pair for testing.

**Returns:**
- (private_key_pem, public_key_pem)


#### `create_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L88"><Icon icon="github" size="14" /></a></sup>

```python
create_token(self, subject: str = 'fastmcp-user', issuer: str = 'https://fastmcp.example.com', audience: str | list[str] | None = None, scopes: list[str] | None = None, expires_in_seconds: int = 3600, additional_claims: dict[str, Any] | None = None, kid: str | None = None) -> str
```

Generate a test JWT token for testing purposes.

**Args:**
- `private_key_pem`: RSA private key in PEM format
- `subject`: Subject claim (usually user ID)
- `issuer`: Issuer claim
- `audience`: Audience claim - can be a string or list of strings (optional)
- `scopes`: List of scopes to include
- `expires_in_seconds`: Token expiration time in seconds
- `additional_claims`: Any additional claims to include
- `kid`: Key ID for JWKS lookup (optional)

**Returns:**
- Signed JWT token string


### `BearerAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L149"><Icon icon="github" size="14" /></a></sup>


Simple JWT Bearer Token validator for hosted MCP servers.
Uses RS256 asymmetric encryption. Supports either static public key
or JWKS URI for key rotation.

Note that this provider DOES NOT permit client registration or revocation, or any OAuth flows.
It is intended to be used with a control plane that manages clients and tokens.

================
File: docs/python-sdk/fastmcp-server-auth-providers-in_memory.mdx
================
---
title: in_memory
sidebarTitle: in_memory
---

# `fastmcp.server.auth.providers.in_memory`

## Classes

### `InMemoryOAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L31"><Icon icon="github" size="14" /></a></sup>


An in-memory OAuth provider for testing purposes.
It simulates the OAuth 2.1 flow locally without external calls.

================
File: docs/python-sdk/fastmcp-server-context.mdx
================
---
title: context
sidebarTitle: context
---

# `fastmcp.server.context`

## Functions

### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L36"><Icon icon="github" size="14" /></a></sup>

```python
set_context(context: Context) -> Generator[Context, None, None]
```

## Classes

### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L45"><Icon icon="github" size="14" /></a></sup>


Context object providing access to MCP capabilities.

This provides a cleaner interface to MCP's RequestContext functionality.
It gets injected into tool and resource functions that request it via type hints.

To use context in a tool function, add a parameter with the Context type annotation:

```python
@server.tool
def my_tool(x: int, ctx: Context) -> str:
    # Log messages to the client
    ctx.info(f"Processing {x}")
    ctx.debug("Debug info")
    ctx.warning("Warning message")
    ctx.error("Error message")

    # Report progress
    ctx.report_progress(50, 100, "Processing")

    # Access resources
    data = ctx.read_resource("resource://data")

    # Get request info
    request_id = ctx.request_id
    client_id = ctx.client_id

    return str(x)
```

The context parameter name can be anything as long as it's annotated with Context.
The context is optional - tools that don't need it can omit the parameter.


**Methods:**

#### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L98"><Icon icon="github" size="14" /></a></sup>

```python
request_context(self) -> RequestContext
```

Access to the underlying request context.

If called outside of a request context, this will raise a ValueError.


#### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L168"><Icon icon="github" size="14" /></a></sup>

```python
client_id(self) -> str | None
```

Get the client ID if available.


#### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L177"><Icon icon="github" size="14" /></a></sup>

```python
request_id(self) -> str
```

Get the unique ID for this request.


#### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L182"><Icon icon="github" size="14" /></a></sup>

```python
session_id(self) -> str | None
```

Get the MCP session ID for HTTP transports.

Returns the session ID that can be used as a key for session-based
data storage (e.g., Redis) to share data between tool calls within
the same client session.

**Returns:**
- The session ID for HTTP transports (SSE, StreamableHTTP), or None
- for stdio and in-memory transports which don't use session IDs.


#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L213"><Icon icon="github" size="14" /></a></sup>

```python
session(self)
```

Access to the underlying session for advanced usage.


#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L282"><Icon icon="github" size="14" /></a></sup>

```python
get_http_request(self) -> Request
```

Get the active starlette request.

================
File: docs/python-sdk/fastmcp-server-dependencies.mdx
================
---
title: dependencies
sidebarTitle: dependencies
---

# `fastmcp.server.dependencies`

## Functions

### `get_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L27"><Icon icon="github" size="14" /></a></sup>

```python
get_context() -> Context
```

### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L39"><Icon icon="github" size="14" /></a></sup>

```python
get_http_request() -> Request
```

### `get_http_headers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L48"><Icon icon="github" size="14" /></a></sup>

```python
get_http_headers(include_all: bool = False) -> dict[str, str]
```


Extract headers from the current HTTP request if available.

Never raises an exception, even if there is no active HTTP request (in which case
an empty dict is returned).

By default, strips problematic headers like `content-length` that cause issues if forwarded to downstream clients.
If `include_all` is True, all headers are returned.

================
File: docs/python-sdk/fastmcp-server-http.mdx
================
---
title: http
sidebarTitle: http
---

# `fastmcp.server.http`

## Functions

### `set_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L48"><Icon icon="github" size="14" /></a></sup>

```python
set_http_request(request: Request) -> Generator[Request, None, None]
```

### `setup_auth_middleware_and_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L72"><Icon icon="github" size="14" /></a></sup>

```python
setup_auth_middleware_and_routes(auth: OAuthProvider) -> tuple[list[Middleware], list[BaseRoute], list[str]]
```


Set up authentication middleware and routes if auth is enabled.

**Args:**
- `auth`: The OAuthProvider authorization server provider

**Returns:**
- Tuple of (middleware, auth_routes, required_scopes)


### `create_base_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L110"><Icon icon="github" size="14" /></a></sup>

```python
create_base_app(routes: list[BaseRoute], middleware: list[Middleware], debug: bool = False, lifespan: Callable | None = None) -> StarletteWithLifespan
```


Create a base Starlette app with common middleware and routes.

**Args:**
- `routes`: List of routes to include in the app
- `middleware`: List of middleware to include in the app
- `debug`: Whether to enable debug mode
- `lifespan`: Optional lifespan manager for the app

**Returns:**
- A Starlette application


### `create_sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L138"><Icon icon="github" size="14" /></a></sup>

```python
create_sse_app(server: FastMCP[LifespanResultT], message_path: str, sse_path: str, auth: OAuthProvider | None = None, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
```


Return an instance of the SSE server app.

**Args:**
- `server`: The FastMCP server instance
- `message_path`: Path for SSE messages
- `sse_path`: Path for SSE connections
- `auth`: Optional auth provider
- `debug`: Whether to enable debug mode
- `routes`: Optional list of custom routes
- `middleware`: Optional list of middleware

Returns:
    A Starlette application with RequestContextMiddleware


### `create_streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L246"><Icon icon="github" size="14" /></a></sup>

```python
create_streamable_http_app(server: FastMCP[LifespanResultT], streamable_http_path: str, event_store: EventStore | None = None, auth: OAuthProvider | None = None, json_response: bool = False, stateless_http: bool = False, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
```


Return an instance of the StreamableHTTP server app.

**Args:**
- `server`: The FastMCP server instance
- `streamable_http_path`: Path for StreamableHTTP connections
- `event_store`: Optional event store for session management
- `auth`: Optional auth provider
- `json_response`: Whether to use JSON response format
- `stateless_http`: Whether to use stateless mode (new transport per request)
- `debug`: Whether to enable debug mode
- `routes`: Optional list of custom routes
- `middleware`: Optional list of middleware

**Returns:**
- A Starlette application with StreamableHTTP support


## Classes

### `StarletteWithLifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L41"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L43"><Icon icon="github" size="14" /></a></sup>

```python
lifespan(self) -> Lifespan
```

### `RequestContextMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L56"><Icon icon="github" size="14" /></a></sup>


Middleware that stores each request in a ContextVar

================
File: docs/python-sdk/fastmcp-server-middleware-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.server.middleware`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-server-middleware-error_handling.mdx
================
---
title: error_handling
sidebarTitle: error_handling
---

# `fastmcp.server.middleware.error_handling`


Error handling middleware for consistent error responses and tracking.

## Classes

### `ErrorHandlingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L15"><Icon icon="github" size="14" /></a></sup>


Middleware that provides consistent error handling and logging.

Catches exceptions, logs them appropriately, and converts them to
proper MCP error responses. Also tracks error patterns for monitoring.


**Methods:**

#### `get_error_stats` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L121"><Icon icon="github" size="14" /></a></sup>

```python
get_error_stats(self) -> dict[str, int]
```

Get error statistics for monitoring.


### `RetryMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L126"><Icon icon="github" size="14" /></a></sup>


Middleware that implements automatic retry logic for failed requests.

Retries requests that fail with transient errors, using exponential
backoff to avoid overwhelming the server or external dependencies.

================
File: docs/python-sdk/fastmcp-server-middleware-logging.mdx
================
---
title: logging
sidebarTitle: logging
---

# `fastmcp.server.middleware.logging`


Comprehensive logging middleware for FastMCP servers.

## Classes

### `LoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L10"><Icon icon="github" size="14" /></a></sup>


Middleware that provides comprehensive request and response logging.

Logs all MCP messages with configurable detail levels. Useful for debugging,
monitoring, and understanding server usage patterns.


### `StructuredLoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L87"><Icon icon="github" size="14" /></a></sup>


Middleware that provides structured JSON logging for better log analysis.

Outputs structured logs that are easier to parse and analyze with log
aggregation tools like ELK stack, Splunk, or cloud logging services.

================
File: docs/python-sdk/fastmcp-server-middleware-middleware.mdx
================
---
title: middleware
sidebarTitle: middleware
---

# `fastmcp.server.middleware.middleware`

## Functions

### `make_middleware_wrapper` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L106"><Icon icon="github" size="14" /></a></sup>

```python
make_middleware_wrapper(middleware: Middleware, call_next: CallNext[T, R]) -> CallNext[T, R]
```


Create a wrapper that applies a single middleware to a context. The
closure bakes in the middleware and call_next function, so it can be
passed to other functions that expect a call_next function.


## Classes

### `CallNext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L36"><Icon icon="github" size="14" /></a></sup>

### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L56"><Icon icon="github" size="14" /></a></sup>

### `ListToolsResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L62"><Icon icon="github" size="14" /></a></sup>

### `ListResourcesResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L67"><Icon icon="github" size="14" /></a></sup>

### `ListResourceTemplatesResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L72"><Icon icon="github" size="14" /></a></sup>

### `ListPromptsResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L77"><Icon icon="github" size="14" /></a></sup>

### `ServerResultProtocol` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L82"><Icon icon="github" size="14" /></a></sup>

### `MiddlewareContext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L87"><Icon icon="github" size="14" /></a></sup>


Unified context for all middleware operations.


**Methods:**

#### `copy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L102"><Icon icon="github" size="14" /></a></sup>

```python
copy(self, **kwargs: Any) -> MiddlewareContext[T]
```

### `Middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L119"><Icon icon="github" size="14" /></a></sup>


Base class for FastMCP middleware with dispatching hooks.

================
File: docs/python-sdk/fastmcp-server-middleware-rate_limiting.mdx
================
---
title: rate_limiting
sidebarTitle: rate_limiting
---

# `fastmcp.server.middleware.rate_limiting`


Rate limiting middleware for protecting FastMCP servers from abuse.

## Classes

### `RateLimitError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L15"><Icon icon="github" size="14" /></a></sup>


Error raised when rate limit is exceeded.


### `TokenBucketRateLimiter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L22"><Icon icon="github" size="14" /></a></sup>


Token bucket implementation for rate limiting.


### `SlidingWindowRateLimiter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L61"><Icon icon="github" size="14" /></a></sup>


Sliding window rate limiter implementation.


### `RateLimitingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L92"><Icon icon="github" size="14" /></a></sup>


Middleware that implements rate limiting to prevent server abuse.

Uses a token bucket algorithm by default, allowing for burst traffic
while maintaining a sustainable long-term rate.


### `SlidingWindowRateLimitingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L170"><Icon icon="github" size="14" /></a></sup>


Middleware that implements sliding window rate limiting.

Uses a sliding window approach which provides more precise rate limiting
but uses more memory to track individual request timestamps.

================
File: docs/python-sdk/fastmcp-server-middleware-timing.mdx
================
---
title: timing
sidebarTitle: timing
---

# `fastmcp.server.middleware.timing`


Timing middleware for measuring and logging request performance.

## Classes

### `TimingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/timing.py#L10"><Icon icon="github" size="14" /></a></sup>


Middleware that logs the execution time of requests.

Only measures and logs timing for request messages (not notifications).
Provides insights into performance characteristics of your MCP server.


### `DetailedTimingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/timing.py#L60"><Icon icon="github" size="14" /></a></sup>


Enhanced timing middleware with per-operation breakdowns.

Provides detailed timing information for different types of MCP operations,
allowing you to identify performance bottlenecks in specific operations.

================
File: docs/python-sdk/fastmcp-server-middleware.mdx
================
---
title: middleware
sidebarTitle: middleware
---

# `fastmcp.server.middleware`

## Functions

### `make_middleware_wrapper`

```python
make_middleware_wrapper(middleware: Middleware, call_next: CallNext[T, R]) -> CallNext[T, R]
```


Create a wrapper that applies a single middleware to a context. The
closure bakes in the middleware and call_next function, so it can be
passed to other functions that expect a call_next function.


## Classes

### `CallNext`

### `CallToolResult`

### `ListToolsResult`

### `ListResourcesResult`

### `ListResourceTemplatesResult`

### `ListPromptsResult`

### `ServerResultProtocol`

### `MiddlewareContext`


Unified context for all middleware operations.


**Methods:**

#### `copy`

```python
copy(self, **kwargs: Any) -> MiddlewareContext[T]
```

### `Middleware`


Base class for FastMCP middleware with dispatching hooks.

================
File: docs/python-sdk/fastmcp-server-openapi.mdx
================
---
title: openapi
sidebarTitle: openapi
---

# `fastmcp.server.openapi`


FastMCP server implementation for OpenAPI integration.

## Classes

### `MCPType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L76"><Icon icon="github" size="14" /></a></sup>


Type of FastMCP component to create from a route.


### `RouteType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L95"><Icon icon="github" size="14" /></a></sup>


Deprecated: Use MCPType instead.

This enum is kept for backward compatibility and will be removed in a future version.


### `RouteMap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L109"><Icon icon="github" size="14" /></a></sup>


Mapping configuration for HTTP routes to FastMCP component types.


### `OpenAPITool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L227"><Icon icon="github" size="14" /></a></sup>


Tool implementation for OpenAPI endpoints.


### `OpenAPIResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L478"><Icon icon="github" size="14" /></a></sup>


Resource implementation for OpenAPI endpoints.


### `OpenAPIResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L597"><Icon icon="github" size="14" /></a></sup>


Resource template implementation for OpenAPI endpoints.


### `FastMCPOpenAPI` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L651"><Icon icon="github" size="14" /></a></sup>


FastMCP server implementation that creates components from an OpenAPI schema.

This class parses an OpenAPI specification and creates appropriate FastMCP components
(Tools, Resources, ResourceTemplates) based on route mappings.

================
File: docs/python-sdk/fastmcp-server-proxy.mdx
================
---
title: proxy
sidebarTitle: proxy
---

# `fastmcp.server.proxy`

## Classes

### `ProxyToolManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L36"><Icon icon="github" size="14" /></a></sup>


A ToolManager that sources its tools from a remote client in addition to local and mounted tools.


### `ProxyResourceManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L81"><Icon icon="github" size="14" /></a></sup>


A ResourceManager that sources its resources from a remote client in addition to local and mounted resources.


### `ProxyPromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L159"><Icon icon="github" size="14" /></a></sup>


A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts.


### `ProxyTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L209"><Icon icon="github" size="14" /></a></sup>


A Tool that represents and executes a tool on a remote server.


**Methods:**

#### `from_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L219"><Icon icon="github" size="14" /></a></sup>

```python
from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
```

Factory method to create a ProxyTool from a raw MCP tool schema.


### `ProxyResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L246"><Icon icon="github" size="14" /></a></sup>


A Resource that represents and reads a resource from a remote server.


**Methods:**

#### `from_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L260"><Icon icon="github" size="14" /></a></sup>

```python
from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource
```

Factory method to create a ProxyResource from a raw MCP resource schema.


### `ProxyTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L287"><Icon icon="github" size="14" /></a></sup>


A ResourceTemplate that represents and creates resources from a remote server template.


**Methods:**

#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L297"><Icon icon="github" size="14" /></a></sup>

```python
from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
```

Factory method to create a ProxyTemplate from a raw MCP template schema.


### `ProxyPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L343"><Icon icon="github" size="14" /></a></sup>


A Prompt that represents and renders a prompt from a remote server.


**Methods:**

#### `from_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L355"><Icon icon="github" size="14" /></a></sup>

```python
from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
```

Factory method to create a ProxyPrompt from a raw MCP prompt schema.


### `FastMCPProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L381"><Icon icon="github" size="14" /></a></sup>


A FastMCP server that acts as a proxy to a remote MCP-compliant server.
It uses specialized managers that fulfill requests via an HTTP client.

================
File: docs/python-sdk/fastmcp-server-server.mdx
================
---
title: server
sidebarTitle: server
---

# `fastmcp.server.server`


FastMCP - A more ergonomic interface for MCP servers.

## Functions

### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1879"><Icon icon="github" size="14" /></a></sup>

```python
add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
```


Add a prefix to a resource URI.

**Args:**
- `uri`: The original resource URI
- `prefix`: The prefix to add

**Returns:**
- The resource URI with the prefix added

**Examples:**

With new style:
```python
add_resource_prefix("resource://path/to/resource", "prefix")
"resource://prefix/path/to/resource"
```
With legacy style:
```python
add_resource_prefix("resource://path/to/resource", "prefix")
"prefix+resource://path/to/resource"
```
With absolute path:
```python
add_resource_prefix("resource:///absolute/path", "prefix")
"resource://prefix//absolute/path"
```

**Raises:**
- `ValueError`: If the URI doesn't match the expected protocol\://path format


### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1939"><Icon icon="github" size="14" /></a></sup>

```python
remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
```


Remove a prefix from a resource URI.

**Args:**
- `uri`: The resource URI with a prefix
- `prefix`: The prefix to remove
- `prefix_format`: The format of the prefix to remove

Returns:
    The resource URI with the prefix removed

**Examples:**

With new style:
```python
remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
"resource://path/to/resource"
```
With legacy style:
```python
remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
"resource://path/to/resource"
```
With absolute path:
```python
remove_resource_prefix("resource://prefix//absolute/path", "prefix")
"resource:///absolute/path"
```

**Raises:**
- `ValueError`: If the URI doesn't match the expected protocol\://path format


### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2006"><Icon icon="github" size="14" /></a></sup>

```python
has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
```


Check if a resource URI has a specific prefix.

**Args:**
- `uri`: The resource URI to check
- `prefix`: The prefix to look for

**Returns:**
- True if the URI has the specified prefix, False otherwise

**Examples:**

With new style:
```python
has_resource_prefix("resource://prefix/path/to/resource", "prefix")
True
```
With legacy style:
```python
has_resource_prefix("prefix+resource://path/to/resource", "prefix")
True
```
With other path:
```python
has_resource_prefix("resource://other/path/to/resource", "prefix")
False
```

**Raises:**
- `ValueError`: If the URI doesn't match the expected protocol\://path format


## Classes

### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L113"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L264"><Icon icon="github" size="14" /></a></sup>

```python
settings(self) -> Settings
```

#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L275"><Icon icon="github" size="14" /></a></sup>

```python
name(self) -> str
```

#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L279"><Icon icon="github" size="14" /></a></sup>

```python
instructions(self) -> str | None
```

#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L304"><Icon icon="github" size="14" /></a></sup>

```python
run(self, transport: Transport | None = None, **transport_kwargs: Any) -> None
```

Run the FastMCP server. Note this is a synchronous function.

**Args:**
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")


#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L338"><Icon icon="github" size="14" /></a></sup>

```python
add_middleware(self, middleware: Middleware) -> None
```

#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L384"><Icon icon="github" size="14" /></a></sup>

```python
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True)
```

Decorator to register a custom HTTP route on the FastMCP server.

Allows adding arbitrary HTTP endpoints outside the standard MCP protocol,
which can be useful for OAuth callbacks, health checks, or admin APIs.
The handler function must be an async function that accepts a Starlette
Request and returns a Response.

**Args:**
- `path`: URL path for the route (e.g., "/oauth/callback")
- `methods`: List of HTTP methods to support (e.g., ["GET", "POST"])
- `name`: Optional name for the route (to reference this route with
Starlette's reverse URL lookup feature)
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True


#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L742"><Icon icon="github" size="14" /></a></sup>

```python
add_tool(self, tool: Tool) -> None
```

Add a tool to the server.

The tool function can optionally request a Context object by adding a parameter
with the Context type annotation. See the @tool decorator for examples.

**Args:**
- `tool`: The Tool instance to register


#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L754"><Icon icon="github" size="14" /></a></sup>

```python
remove_tool(self, name: str) -> None
```

Remove a tool from the server.

**Args:**
- `name`: The name of the tool to remove

**Raises:**
- `NotFoundError`: If the tool is not found


#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L767"><Icon icon="github" size="14" /></a></sup>

```python
tool(self, name_or_fn: AnyFunction) -> FunctionTool
```

#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L780"><Icon icon="github" size="14" /></a></sup>

```python
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
```

#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L792"><Icon icon="github" size="14" /></a></sup>

```python
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
```

Decorator to register a tool.

Tools can optionally request a Context object by adding a parameter with the
Context type annotation. The context provides access to MCP capabilities like
logging, progress reporting, and resource access.

This decorator supports multiple calling patterns:
- @server.tool (without parentheses)
- @server.tool (with empty parentheses)
- @server.tool("custom_name") (with name as first argument)
- @server.tool(name="custom_name") (with name as keyword argument)
- server.tool(function, name="custom_name") (direct function call)

**Args:**
- `name_or_fn`: Either a function (when used as @tool), a string name, or None
- `name`: Optional name for the tool (keyword-only, alternative to name_or_fn)
- `description`: Optional description of what the tool does
- `tags`: Optional set of tags for categorizing the tool
- `annotations`: Optional annotations about the tool's behavior
- `exclude_args`: Optional list of argument names to exclude from the tool schema
- `enabled`: Optional boolean to enable or disable the tool

**Examples:**

Register a tool with a custom name:
```python
@server.tool
def my_tool(x: int) -> str:
    return str(x)

# Register a tool with a custom name
@server.tool
def my_tool(x: int) -> str:
    return str(x)

@server.tool("custom_name")
def my_tool(x: int) -> str:
    return str(x)

@server.tool(name="custom_name")
def my_tool(x: int) -> str:
    return str(x)

# Direct function call
server.tool(my_function, name="custom_name")
```


#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L912"><Icon icon="github" size="14" /></a></sup>

```python
add_resource(self, resource: Resource) -> None
```

Add a resource to the server.

**Args:**
- `resource`: A Resource instance to add


#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L922"><Icon icon="github" size="14" /></a></sup>

```python
add_template(self, template: ResourceTemplate) -> None
```

Add a resource template to the server.

**Args:**
- `template`: A ResourceTemplate instance to add


#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L930"><Icon icon="github" size="14" /></a></sup>

```python
add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
```

Add a resource or template to the server from a function.

If the URI contains parameters (e.g. "resource://{param}") or the function
has parameters, it will be registered as a template resource.

**Args:**
- `fn`: The function to register as a resource
- `uri`: The URI for the resource
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource


#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L969"><Icon icon="github" size="14" /></a></sup>

```python
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
```

Decorator to register a function as a resource.

The function will be called when the resource is read to generate its content.
The function can return:
- str for text content
- bytes for binary content
- other types will be converted to JSON

Resources can optionally request a Context object by adding a parameter with the
Context type annotation. The context provides access to MCP capabilities like
logging, progress reporting, and session information.

If the URI contains parameters (e.g. "resource://{param}") or the function
has parameters, it will be registered as a template resource.

**Args:**
- `uri`: URI for the resource (e.g. "resource\://my-resource" or "resource\://{param}")
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource
- `enabled`: Optional boolean to enable or disable the resource

**Examples:**

Register a resource with a custom name:
```python
@server.resource("resource://my-resource")
def get_data() -> str:
    return "Hello, world!"

@server.resource("resource://my-resource")
async get_data() -> str:
    data = await fetch_data()
    return f"Hello, world! {data}"

@server.resource("resource://{city}/weather")
def get_weather(city: str) -> str:
    return f"Weather for {city}"

@server.resource("resource://{city}/weather")
def get_weather_with_context(city: str, ctx: Context) -> str:
    ctx.info(f"Fetching weather for {city}")
    return f"Weather for {city}"

@server.resource("resource://{city}/weather")
async def get_weather(city: str) -> str:
    data = await fetch_weather(city)
    return f"Weather for {city}: {data}"
```


#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1092"><Icon icon="github" size="14" /></a></sup>

```python
add_prompt(self, prompt: Prompt) -> None
```

Add a prompt to the server.

**Args:**
- `prompt`: A Prompt instance to add


#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1102"><Icon icon="github" size="14" /></a></sup>

```python
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
```

#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1113"><Icon icon="github" size="14" /></a></sup>

```python
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
```

#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1123"><Icon icon="github" size="14" /></a></sup>

```python
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
```

Decorator to register a prompt.

        Prompts can optionally request a Context object by adding a parameter with the
        Context type annotation. The context provides access to MCP capabilities like
        logging, progress reporting, and session information.

        This decorator supports multiple calling patterns:
        - @server.prompt (without parentheses)
        - @server.prompt() (with empty parentheses)
        - @server.prompt("custom_name") (with name as first argument)
        - @server.prompt(name="custom_name") (with name as keyword argument)
        - server.prompt(function, name="custom_name") (direct function call)

        Args:
            name_or_fn: Either a function (when used as @prompt), a string name, or None
            name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
            description: Optional description of what the prompt does
            tags: Optional set of tags for categorizing the prompt
            enabled: Optional boolean to enable or disable the prompt

        Examples:

            ```python
            @server.prompt
            def analyze_table(table_name: str) -> list[Message]:
                schema = read_table_schema(table_name)
                return [
                    {
                        "role": "user",
                        "content": f"Analyze this schema:
{schema}"
                    }
                ]

            @server.prompt()
            def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
                ctx.info(f"Analyzing table {table_name}")
                schema = read_table_schema(table_name)
                return [
                    {
                        "role": "user",
                        "content": f"Analyze this schema:
{schema}"
                    }
                ]

            @server.prompt("custom_name")
            def analyze_file(path: str) -> list[Message]:
                content = await read_file(path)
                return [
                    {
                        "role": "user",
                        "content": {
                            "type": "resource",
                            "resource": {
                                "uri": f"file://{path}",
                                "text": content
                            }
                        }
                    }
                ]

            @server.prompt(name="custom_name")
            def another_prompt(data: str) -> list[Message]:
                return [{"role": "user", "content": data}]

            # Direct function call
            server.prompt(my_function, name="custom_name")
            ```


#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1344"><Icon icon="github" size="14" /></a></sup>

```python
sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
```

Create a Starlette app for the SSE server.

**Args:**
- `path`: The path to the SSE endpoint
- `message_path`: The path to the message endpoint
- `middleware`: A list of middleware to apply to the app


#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1375"><Icon icon="github" size="14" /></a></sup>

```python
streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
```

Create a Starlette app for the StreamableHTTP server.

**Args:**
- `path`: The path to the StreamableHTTP endpoint
- `middleware`: A list of middleware to apply to the app


#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1396"><Icon icon="github" size="14" /></a></sup>

```python
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
```

Create a Starlette app using the specified HTTP transport.

**Args:**
- `path`: The path for the HTTP endpoint
- `middleware`: A list of middleware to apply to the app
- `transport`: Transport protocol to use - either "streamable-http" (default) or "sse"

**Returns:**
- A Starlette application configured with the specified transport


#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1470"><Icon icon="github" size="14" /></a></sup>

```python
mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
```

Mount another FastMCP server on this server with an optional prefix.

Unlike importing (with import_server), mounting establishes a dynamic connection
between servers. When a client interacts with a mounted server's objects through
the parent server, requests are forwarded to the mounted server in real-time.
This means changes to the mounted server are immediately reflected when accessed
through the parent.

When a server is mounted with a prefix:
- Tools from the mounted server are accessible with prefixed names.
  Example: If server has a tool named "get_weather", it will be available as "prefix_get_weather".
- Resources are accessible with prefixed URIs.
  Example: If server has a resource with URI "weather://forecast", it will be available as
  "weather://prefix/forecast".
- Templates are accessible with prefixed URI templates.
  Example: If server has a template with URI "weather://location/{id}", it will be available
  as "weather://prefix/location/{id}".
- Prompts are accessible with prefixed names.
  Example: If server has a prompt named "weather_prompt", it will be available as
  "prefix_weather_prompt".

When a server is mounted without a prefix (prefix=None), its tools, resources, templates,
and prompts are accessible with their original names. Multiple servers can be mounted
without prefixes, and they will be tried in order until a match is found.

There are two modes for mounting servers:
1. Direct mounting (default when server has no custom lifespan): The parent server
   directly accesses the mounted server's objects in-memory for better performance.
   In this mode, no client lifecycle events occur on the mounted server, including
   lifespan execution.

2. Proxy mounting (default when server has a custom lifespan): The parent server
   treats the mounted server as a separate entity and communicates with it via a
   Client transport. This preserves all client-facing behaviors, including lifespan
   execution, but with slightly higher overhead.

**Args:**
- `server`: The FastMCP server to mount.
- `prefix`: Optional prefix to use for the mounted server's objects. If None,
the server's objects are accessible with their original names.
- `as_proxy`: Whether to treat the mounted server as a proxy. If None (default),
automatically determined based on whether the server has a custom lifespan
(True if it has a custom lifespan, False otherwise).
- `tool_separator`: Deprecated. Separator character for tool names.
- `resource_separator`: Deprecated. Separator character for resource URIs.
- `prompt_separator`: Deprecated. Separator character for prompt names.


#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1720"><Icon icon="github" size="14" /></a></sup>

```python
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
```

Create a FastMCP server from an OpenAPI specification.


#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1748"><Icon icon="github" size="14" /></a></sup>

```python
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
```

Create a FastMCP server from a FastAPI application.


#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1790"><Icon icon="github" size="14" /></a></sup>

```python
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
```

Create a FastMCP proxy server for the given backend.

The `backend` argument can be either an existing `fastmcp.client.Client`
instance or any value accepted as the `transport` argument of
`fastmcp.client.Client`. This mirrors the convenience of the
`fastmcp.client.Client` constructor.


#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1820"><Icon icon="github" size="14" /></a></sup>

```python
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
```

Create a FastMCP proxy server from a FastMCP client.


### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1873"><Icon icon="github" size="14" /></a></sup>

================
File: docs/python-sdk/fastmcp-settings.mdx
================
---
title: settings
sidebarTitle: settings
---

# `fastmcp.settings`

## Classes

### `ExtendedEnvSettingsSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L26"><Icon icon="github" size="14" /></a></sup>


A special EnvSettingsSource that allows for multiple env var prefixes to be used.

Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.


**Methods:**

#### `get_field_value` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L33"><Icon icon="github" size="14" /></a></sup>

```python
get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]
```

### `ExtendedSettingsConfigDict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L53"><Icon icon="github" size="14" /></a></sup>

### `Settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L57"><Icon icon="github" size="14" /></a></sup>


FastMCP settings.


**Methods:**

#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L69"><Icon icon="github" size="14" /></a></sup>

```python
settings_customise_sources(cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource) -> tuple[PydanticBaseSettingsSource, ...]
```

#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L87"><Icon icon="github" size="14" /></a></sup>

```python
settings(self) -> Self
```

This property is for backwards compatibility with FastMCP < 2.8.0,
which accessed fastmcp.settings.settings


#### `setup_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L182"><Icon icon="github" size="14" /></a></sup>

```python
setup_logging(self) -> Self
```

Finalize the settings.

================
File: docs/python-sdk/fastmcp-tools-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.tools`

*This module is empty or contains only private/internal implementations.*

================
File: docs/python-sdk/fastmcp-tools-tool_manager.mdx
================
---
title: tool_manager
sidebarTitle: tool_manager
---

# `fastmcp.tools.tool_manager`

## Classes

### `ToolManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L22"><Icon icon="github" size="14" /></a></sup>


Manages FastMCP tools.


**Methods:**

#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L46"><Icon icon="github" size="14" /></a></sup>

```python
mount(self, server: MountedServer) -> None
```

Adds a mounted server as a source for tools.


#### `add_tool_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L113"><Icon icon="github" size="14" /></a></sup>

```python
add_tool_from_fn(self, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, exclude_args: list[str] | None = None) -> Tool
```

Add a tool to the server.


#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L142"><Icon icon="github" size="14" /></a></sup>

```python
add_tool(self, tool: Tool) -> Tool
```

Register a tool with the server.


#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L159"><Icon icon="github" size="14" /></a></sup>

```python
remove_tool(self, key: str) -> None
```

Remove a tool from the server.

**Args:**
- `key`: The key of the tool to remove

**Raises:**
- `NotFoundError`: If the tool is not found

================
File: docs/python-sdk/fastmcp-tools-tool_transform.mdx
================
---
title: tool_transform
sidebarTitle: tool_transform
---

# `fastmcp.tools.tool_transform`

## Classes

### `ArgTransform` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L85"><Icon icon="github" size="14" /></a></sup>


Configuration for transforming a parent tool's argument.

This class allows fine-grained control over how individual arguments are transformed
when creating a new tool from an existing one. You can rename arguments, change their
descriptions, add default values, or hide them from clients while passing constants.

**Examples:**

Rename argument 'old_name' to 'new_name'
```python
ArgTransform(name="new_name")
```

Change description only
```python
ArgTransform(description="Updated description")
```

Add a default value (makes argument optional)
```python
ArgTransform(default=42)
```

Add a default factory (makes argument optional)
```python
ArgTransform(default_factory=lambda: time.time())
```

Change the type
```python
ArgTransform(type=str)
```

Hide the argument entirely from clients
```python
ArgTransform(hide=True)
```

Hide argument but pass a constant value to parent
```python
ArgTransform(hide=True, default="constant_value")
```

Hide argument but pass a factory-generated value to parent
```python
ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
```

Make an optional parameter required (removes any default)
```python
ArgTransform(required=True)
```

Combine multiple transformations
```python
ArgTransform(name="new_name", description="New desc", default=None, type=int)
```


### `TransformedTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L199"><Icon icon="github" size="14" /></a></sup>


A tool that is transformed from another tool.

This class represents a tool that has been created by transforming another tool.
It supports argument renaming, schema modification, custom function injection,
and provides context for the forward() and forward_raw() functions.

The transformation can be purely schema-based (argument renaming, dropping, etc.)
or can include a custom function that uses forward() to call the parent tool
with transformed arguments.


**Methods:**

#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L280"><Icon icon="github" size="14" /></a></sup>

```python
from_tool(cls, tool: Tool, name: str | None = None, description: str | None = None, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
```

Create a transformed tool from a parent tool.

**Args:**
- `tool`: The parent tool to transform.
- `transform_fn`: Optional custom function. Can use forward() and forward_raw()
to call the parent tool. Functions with **kwargs receive transformed
argument names.
- `name`: New name for the tool. Defaults to parent tool's name.
- `transform_args`: Optional transformations for parent tool arguments.
Only specified arguments are transformed, others pass through unchanged\:
- Simple rename (str)
- Complex transformation (rename/description/default/drop) (ArgTransform)
- Drop the argument (None)
- `description`: New description. Defaults to parent's description.
- `tags`: New tags. Defaults to parent's tags.
- `annotations`: New annotations. Defaults to parent's annotations.
- `serializer`: New serializer. Defaults to parent's serializer.

**Returns:**
- TransformedTool with the specified transformations.

**Examples:**

# Transform specific arguments only
```python
Tool.from_tool(parent, transform_args={"old": "new"})  # Others unchanged
```

# Custom function with partial transforms
```python
async def custom(x: int, y: int) -> str:
    result = await forward(x=x, y=y)
    return f"Custom: {result}"

Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
```

# Using **kwargs (gets all args, transformed and untransformed)
```python
async def flexible(**kwargs) -> str:
    result = await forward(**kwargs)
    return f"Got: {kwargs}"

Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
```

================
File: docs/python-sdk/fastmcp-tools-tool.mdx
================
---
title: tool
sidebarTitle: tool
---

# `fastmcp.tools.tool`

## Functions

### `default_serializer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L34"><Icon icon="github" size="14" /></a></sup>

```python
default_serializer(data: Any) -> str
```

## Classes

### `Tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L38"><Icon icon="github" size="14" /></a></sup>


Internal tool registration info.


**Methods:**

#### `to_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L49"><Icon icon="github" size="14" /></a></sup>

```python
to_mcp_tool(self, **overrides: Any) -> MCPTool
```

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L59"><Icon icon="github" size="14" /></a></sup>

```python
from_function(fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
```

Create a Tool from a function.


#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L86"><Icon icon="github" size="14" /></a></sup>

```python
from_tool(cls, tool: Tool, transform_fn: Callable[..., Any] | None = None, name: str | None = None, transform_args: dict[str, ArgTransform] | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
```

### `FunctionTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L113"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L117"><Icon icon="github" size="14" /></a></sup>

```python
from_function(cls, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
```

Create a Tool from a function.


### `ParsedFunction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L194"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L201"><Icon icon="github" size="14" /></a></sup>

```python
from_function(cls, fn: Callable[..., Any], exclude_args: list[str] | None = None, validate: bool = True) -> ParsedFunction
```

================
File: docs/python-sdk/fastmcp-utilities-__init__.mdx
================
---
title: __init__
sidebarTitle: __init__
---

# `fastmcp.utilities`


FastMCP utility modules.

================
File: docs/python-sdk/fastmcp-utilities-cache.mdx
================
---
title: cache
sidebarTitle: cache
---

# `fastmcp.utilities.cache`

## Classes

### `TimedCache` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L7"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `set` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L14"><Icon icon="github" size="14" /></a></sup>

```python
set(self, key: Any, value: Any) -> None
```

#### `get` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L18"><Icon icon="github" size="14" /></a></sup>

```python
get(self, key: Any) -> Any
```

#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L25"><Icon icon="github" size="14" /></a></sup>

```python
clear(self) -> None
```

================
File: docs/python-sdk/fastmcp-utilities-components.mdx
================
---
title: components
sidebarTitle: components
---

# `fastmcp.utilities.components`

## Classes

### `FastMCPComponent` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L21"><Icon icon="github" size="14" /></a></sup>


Base class for FastMCP tools, prompts, resources, and resource templates.


**Methods:**

#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L48"><Icon icon="github" size="14" /></a></sup>

```python
key(self) -> str
```

The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.


#### `with_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L57"><Icon icon="github" size="14" /></a></sup>

```python
with_key(self, key: str) -> Self
```

#### `enable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L69"><Icon icon="github" size="14" /></a></sup>

```python
enable(self) -> None
```

Enable the component.


#### `disable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L73"><Icon icon="github" size="14" /></a></sup>

```python
disable(self) -> None
```

Disable the component.

================
File: docs/python-sdk/fastmcp-utilities-exceptions.mdx
================
---
title: exceptions
sidebarTitle: exceptions
---

# `fastmcp.utilities.exceptions`

## Functions

### `iter_exc` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/exceptions.py#L12"><Icon icon="github" size="14" /></a></sup>

```python
iter_exc(group: BaseExceptionGroup)
```

### `get_catch_handlers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/exceptions.py#L42"><Icon icon="github" size="14" /></a></sup>

```python
get_catch_handlers() -> Mapping[type[BaseException] | Iterable[type[BaseException]], Callable[[BaseExceptionGroup[Any]], Any]]
```

================
File: docs/python-sdk/fastmcp-utilities-http.mdx
================
---
title: http
sidebarTitle: http
---

# `fastmcp.utilities.http`

## Functions

### `find_available_port` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/http.py#L4"><Icon icon="github" size="14" /></a></sup>

```python
find_available_port() -> int
```


Find an available port by letting the OS assign one.

================
File: docs/python-sdk/fastmcp-utilities-inspect.mdx
================
---
title: inspect
sidebarTitle: inspect
---

# `fastmcp.utilities.inspect`


Utilities for inspecting FastMCP instances.

## Classes

### `ToolInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L16"><Icon icon="github" size="14" /></a></sup>


Information about a tool.


### `PromptInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L29"><Icon icon="github" size="14" /></a></sup>


Information about a prompt.


### `ResourceInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L41"><Icon icon="github" size="14" /></a></sup>


Information about a resource.


### `TemplateInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L54"><Icon icon="github" size="14" /></a></sup>


Information about a resource template.


### `FastMCPInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L67"><Icon icon="github" size="14" /></a></sup>


Information extracted from a FastMCP instance.

================
File: docs/python-sdk/fastmcp-utilities-json_schema.mdx
================
---
title: json_schema
sidebarTitle: json_schema
---

# `fastmcp.utilities.json_schema`

## Functions

### `compress_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/json_schema.py#L130"><Icon icon="github" size="14" /></a></sup>

```python
compress_schema(schema: dict, prune_params: list[str] | None = None, prune_defs: bool = True, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict
```


Remove the given parameters from the schema.

**Args:**
- `schema`: The schema to compress
- `prune_params`: List of parameter names to remove from properties
- `prune_defs`: Whether to remove unused definitions
- `prune_additional_properties`: Whether to remove additionalProperties\: false
- `prune_titles`: Whether to remove title fields from the schema

================
File: docs/python-sdk/fastmcp-utilities-logging.mdx
================
---
title: logging
sidebarTitle: logging
---

# `fastmcp.utilities.logging`


Logging utilities for FastMCP.

## Functions

### `get_logger` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L10"><Icon icon="github" size="14" /></a></sup>

```python
get_logger(name: str) -> logging.Logger
```


Get a logger nested under FastMCP namespace.

**Args:**
- `name`: the name of the logger, which will be prefixed with 'FastMCP.'

**Returns:**
- a configured logger instance


### `configure_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L22"><Icon icon="github" size="14" /></a></sup>

```python
configure_logging(level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | int = 'INFO', logger: logging.Logger | None = None, enable_rich_tracebacks: bool = True) -> None
```


Configure logging for FastMCP.

**Args:**
- `logger`: the logger to configure
- `level`: the log level to use

================
File: docs/python-sdk/fastmcp-utilities-mcp_config.mdx
================
---
title: mcp_config
sidebarTitle: mcp_config
---

# `fastmcp.utilities.mcp_config`

## Functions

### `infer_transport_type_from_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L20"><Icon icon="github" size="14" /></a></sup>

```python
infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
```


Infer the appropriate transport type from the given URL.


## Classes

### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L40"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L47"><Icon icon="github" size="14" /></a></sup>

```python
to_transport(self) -> StdioTransport
```

### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L58"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L71"><Icon icon="github" size="14" /></a></sup>

```python
to_transport(self) -> StreamableHttpTransport | SSETransport
```

### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L88"><Icon icon="github" size="14" /></a></sup>

**Methods:**

#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L92"><Icon icon="github" size="14" /></a></sup>

```python
from_dict(cls, config: dict[str, Any]) -> MCPConfig
```

================
File: docs/python-sdk/fastmcp-utilities-openapi.mdx
================
---
title: openapi
sidebarTitle: openapi
---

# `fastmcp.utilities.openapi`

## Functions

### `parse_openapi_to_http_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L112"><Icon icon="github" size="14" /></a></sup>

```python
parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute]
```


Parses an OpenAPI schema dictionary into a list of HTTPRoute objects
using the openapi-pydantic library.

Supports both OpenAPI 3.0.x and 3.1.x versions.


### `clean_schema_for_display` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L570"><Icon icon="github" size="14" /></a></sup>

```python
clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None
```


Clean up a schema dictionary for display by removing internal/complex fields.


### `generate_example_from_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L630"><Icon icon="github" size="14" /></a></sup>

```python
generate_example_from_schema(schema: JsonSchema | None) -> Any
```


Generate a simple example value from a JSON schema dictionary.
Very basic implementation focusing on types.


### `format_json_for_description` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L713"><Icon icon="github" size="14" /></a></sup>

```python
format_json_for_description(data: Any, indent: int = 2) -> str
```


Formats Python data as a JSON string block for markdown.


### `format_description_with_responses` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L722"><Icon icon="github" size="14" /></a></sup>

```python
format_description_with_responses(base_description: str, responses: dict[str, Any], parameters: list[ParameterInfo] | None = None, request_body: RequestBodyInfo | None = None) -> str
```


Formats the base description string with response, parameter, and request body information.

**Args:**
- `base_description`: The initial description to be formatted.
- `responses`: A dictionary of response information, keyed by status code.
- `parameters`: A list of parameter information,
including path and query parameters. Each parameter includes details such as name,
location, whether it is required, and a description.
- `request_body`: Information about the request body,
including its description, whether it is required, and its content schema.

**Returns:**
- The formatted description string with additional details about responses, parameters,
- and the request body.


## Classes

### `ParameterInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L42"><Icon icon="github" size="14" /></a></sup>


Represents a single parameter for an HTTP operation in our IR.


### `RequestBodyInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L52"><Icon icon="github" size="14" /></a></sup>


Represents the request body for an HTTP operation in our IR.


### `ResponseInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L62"><Icon icon="github" size="14" /></a></sup>


Represents response information in our IR.


### `HTTPRoute` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L70"><Icon icon="github" size="14" /></a></sup>


Intermediate Representation for a single OpenAPI operation.


### `OpenAPIParser` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L164"><Icon icon="github" size="14" /></a></sup>


Unified parser for OpenAPI schemas with generic type parameters to handle both 3.0 and 3.1.


**Methods:**

#### `parse` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L469"><Icon icon="github" size="14" /></a></sup>

```python
parse(self) -> list[HTTPRoute]
```

Parse the OpenAPI schema into HTTP routes.

================
File: docs/python-sdk/fastmcp-utilities-tests.mdx
================
---
title: tests
sidebarTitle: tests
---

# `fastmcp.utilities.tests`

## Functions

### `temporary_settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L21"><Icon icon="github" size="14" /></a></sup>

```python
temporary_settings(**kwargs: Any)
```


Temporarily override FastMCP setting values.

**Args:**
- `**kwargs`: The settings to override, including nested settings.


### `run_server_in_process` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L74"><Icon icon="github" size="14" /></a></sup>

```python
run_server_in_process(server_fn: Callable[..., None], *args, **kwargs) -> Generator[str, None, None]
```


Context manager that runs a FastMCP server in a separate process and
returns the server URL. When the context manager is exited, the server process is killed.

**Args:**
- `server_fn`: The function that runs a FastMCP server. FastMCP servers are
not pickleable, so we need a function that creates and runs one.
- `*args`: Arguments to pass to the server function.
- `provide_host_and_port`: Whether to provide the host and port to the server function as kwargs.
- `**kwargs`: Keyword arguments to pass to the server function.

**Returns:**
- The server URL.

================
File: docs/python-sdk/fastmcp-utilities-types.mdx
================
---
title: types
sidebarTitle: types
---

# `fastmcp.utilities.types`


Common types used across FastMCP.

## Functions

### `get_cached_typeadapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L35"><Icon icon="github" size="14" /></a></sup>

```python
get_cached_typeadapter(cls: T) -> TypeAdapter[T]
```


TypeAdapters are heavy objects, and in an application context we'd typically
create them once in a global scope and reuse them as often as possible.
However, this isn't feasible for user-generated functions. Instead, we use a
cache to minimize the cost of creating them as much as possible.


### `issubclass_safe` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L45"><Icon icon="github" size="14" /></a></sup>

```python
issubclass_safe(cls: type, base: type) -> bool
```


Check if cls is a subclass of base, even if cls is a type variable.


### `is_class_member_of_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L55"><Icon icon="github" size="14" /></a></sup>

```python
is_class_member_of_type(cls: type, base: type) -> bool
```


Check if cls is a member of base, even if cls is a type variable.

Base can be a type, a UnionType, or an Annotated type. Generic types are not
considered members (e.g. T is not a member of list\[T]).


### `find_kwarg_by_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L77"><Icon icon="github" size="14" /></a></sup>

```python
find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None
```


Find the name of the kwarg that is of type kwarg_type.

Includes union types that contain the kwarg_type, as well as Annotated types.


## Classes

### `FastMCPBaseModel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L28"><Icon icon="github" size="14" /></a></sup>


Base model for FastMCP models.


### `Image` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L94"><Icon icon="github" size="14" /></a></sup>


Helper class for returning images from tools.


**Methods:**

#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L131"><Icon icon="github" size="14" /></a></sup>

```python
to_image_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> ImageContent
```

Convert to MCP ImageContent.


### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L153"><Icon icon="github" size="14" /></a></sup>


Helper class for returning audio from tools.


**Methods:**

#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L190"><Icon icon="github" size="14" /></a></sup>

```python
to_audio_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> AudioContent
```

### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L211"><Icon icon="github" size="14" /></a></sup>


Helper class for returning audio from tools.


**Methods:**

#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L250"><Icon icon="github" size="14" /></a></sup>

```python
to_resource_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> EmbeddedResource
```

================
File: docs/servers/auth/bearer.mdx
================
---
title: Bearer Token Authentication
sidebarTitle: Bearer Auth
description: Secure your FastMCP server's HTTP endpoints by validating JWT Bearer tokens.
icon: key
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.6.0" />
<Tip>
Authentication and authorization are only relevant for HTTP-based transports.
</Tip>

<Note>
The [MCP specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) requires servers to implement full OAuth 2.1 authorization flows with dynamic client registration, server metadata discovery, and complete token endpoints. FastMCP's Bearer Token authentication provides a simpler, more practical alternative by directly validating pre-issued JWT tokens—ideal for service-to-service communication and programmatic environments where full OAuth flows may be impractical, and in accordance with how the MCP ecosystem is pragmatically evolving. However, please note that since it doesn't implement the full OAuth 2.1 flow, this implementation does not strictly comply with the MCP specification.
</Note>

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.

FastMCP supports Bearer Token authentication for its HTTP-based transports (`http` and `sse`), allowing you to protect your server from unauthorized access.

## Authentication Strategy

FastMCP uses **asymmetric encryption** for token validation, which provides a clean security separation between token issuers and FastMCP servers. This approach means:

- **No shared secrets**: Your FastMCP server never needs access to private keys or client secrets
- **Public key verification**: The server only needs a public key (or JWKS endpoint) to verify token signatures
- **Secure token issuance**: Tokens are signed by an external service using a private key that never leaves the issuer
- **Scalable architecture**: Multiple FastMCP servers can validate tokens without coordinating secrets

This design allows you to integrate FastMCP servers into existing authentication infrastructures without compromising security boundaries.

## Configuration

To enable Bearer Token validation on your FastMCP server, use the `BearerAuthProvider` class. This provider validates incoming JWTs by verifying signatures, checking expiration, and optionally validating claims.

<Warning>
The `BearerAuthProvider` validates tokens; it does **not** issue them (or implement any part of an OAuth flow). You'll need to generate tokens separately, either using FastMCP utilities or an external Identity Provider (IdP) or OAuth 2.1 Authorization Server.
</Warning>

### Basic Setup

To configure bearer token authentication, instantiate a `BearerAuthProvider` instance and pass it to the `auth` parameter of the `FastMCP` instance.

The `BearerAuthProvider` requires either a static public key or a JWKS URI (but not both!) in order to verify the token's signature. All other parameters are optional -- if they are provided, they will be used as additional validation criteria.

```python {2, 10}
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider

auth = BearerAuthProvider(
    jwks_uri="https://my-identity-provider.com/.well-known/jwks.json",
    issuer="https://my-identity-provider.com/",
    audience="my-mcp-server"
)

mcp = FastMCP(name="My MCP Server", auth=auth)
```

### Configuration Parameters

<Card icon="code" title="BearerAuthProvider Configuration">
<ParamField body="public_key" type="str">
  RSA public key in PEM format for static key validation. Required if `jwks_uri` is not provided
</ParamField>

<ParamField body="jwks_uri" type="str">
  URL for JSON Web Key Set endpoint. Required if `public_key` is not provided
</ParamField>

<ParamField body="issuer" type="str | None">
  Expected JWT `iss` claim value
</ParamField>

<ParamField body="audience" type="str | None">
  Expected JWT `aud` claim value
</ParamField>

<ParamField body="required_scopes" type="list[str] | None">
  Global scopes required for all requests
</ParamField>
</Card>

#### Public Key

If you have a public key in PEM format, you can provide it to the `BearerAuthProvider` as a string.

```python {12}
from fastmcp.server.auth import BearerAuthProvider
import inspect

public_key_pem = inspect.cleandoc(
    """
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy...
    -----END PUBLIC KEY-----
    """
)

auth = BearerAuthProvider(public_key=public_key_pem)
```

#### JWKS URI

```python
provider = BearerAuthProvider(
    jwks_uri="https://idp.example.com/.well-known/jwks.json"
)
```

<Note>
JWKS is recommended for production as it supports automatic key rotation and multiple signing keys.
</Note>

## Generating Tokens

For development and testing, FastMCP provides the `RSAKeyPair` utility class to generate tokens without needing an external OAuth provider.

<Warning>
The `RSAKeyPair` utility is intended for development and testing only. For production, use a proper OAuth 2.1 Authorization Server or Identity Provider.
</Warning>
### Basic Token Generation

```python
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.auth.providers.bearer import RSAKeyPair

# Generate a new key pair
key_pair = RSAKeyPair.generate()

# Configure the auth provider with the public key
auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    issuer="https://dev.example.com",
    audience="my-dev-server"
)

mcp = FastMCP(name="Development Server", auth=auth)

# Generate a token for testing
token = key_pair.create_token(
    subject="dev-user",
    issuer="https://dev.example.com",
    audience="my-dev-server",
    scopes=["read", "write"]
)

print(f"Test token: {token}")
```

### Token Creation Parameters

The `create_token()` method accepts these parameters:

<Card icon="code" title="create_token() Parameters">
<ParamField body="subject" type="str" default="fastmcp-user">
  JWT subject claim (usually user ID)
</ParamField>

<ParamField body="issuer" type="str" default="https://fastmcp.example.com">
  JWT issuer claim
</ParamField>

<ParamField body="audience" type="str | None">
  JWT audience claim
</ParamField>

<ParamField body="scopes" type="list[str] | None">
  OAuth scopes to include
</ParamField>

<ParamField body="expires_in_seconds" type="int" default="3600">
  Token expiration time in seconds
</ParamField>

<ParamField body="additional_claims" type="dict | None">
  Extra claims to include in the token
</ParamField>

<ParamField body="kid" type="str | None">
  Key ID for JWKS lookup
</ParamField>
</Card>


## Accessing Token Claims

Once authenticated, your tools, resources, or prompts can access token information using the `get_access_token()` dependency function:

```python
from fastmcp import FastMCP, Context, ToolError
from fastmcp.server.dependencies import get_access_token, AccessToken

@mcp.tool
async def get_my_data(ctx: Context) -> dict:
    access_token: AccessToken = get_access_token()
    
    user_id = access_token.client_id  # From JWT 'sub' or 'client_id' claim
    user_scopes = access_token.scopes
    
    if "data:read_sensitive" not in user_scopes:
        raise ToolError("Insufficient permissions: 'data:read_sensitive' scope required.")
    
    return {
        "user": user_id,
        "sensitive_data": f"Private data for {user_id}",
        "granted_scopes": user_scopes
    }
```

### AccessToken Properties

<Card icon="code" title="AccessToken Properties">
<ParamField body="token" type="str">
  The raw JWT string
</ParamField>

<ParamField body="client_id" type="str">
  Authenticated principal identifier
</ParamField>

<ParamField body="scopes" type="list[str]">
  Granted scopes
</ParamField>

<ParamField body="expires_at" type="datetime | None">
  Token expiration timestamp
</ParamField>
</Card>

================
File: docs/servers/composition.mdx
================
---
title: Server Composition
sidebarTitle: Composition
description: Combine multiple FastMCP servers into a single, larger application using mounting and importing.
icon: puzzle-piece
---
import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.2.0" />

As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through two methods:

- **`import_server`**: For a one-time copy of components with prefixing (static composition).
- **`mount`**: For creating a live link where the main server delegates requests to the subserver (dynamic composition).

## Why Compose Servers?

-   **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`).
-   **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed.
-   **Teamwork**: Different teams can work on separate FastMCP servers that are later combined.
-   **Organization**: Keep related functionality grouped together logically.

### Importing vs Mounting

The choice of importing or mounting depends on your use case and requirements.

| Feature | Importing | Mounting |
|---------|----------------|---------|
| **Method** | `FastMCP.import_server(server, prefix=None)` | `FastMCP.mount(server, prefix=None)` |
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
| **Prefix** | Optional - omit for original names | Optional - omit for original names |
| **Best For** | Bundling finalized components | Modular runtime composition |

### Proxy Servers

FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a local or remote server in a local FastMCP instance. Proxies are fully compatible with both importing and mounting.

<VersionBadge version="2.4.0" />

You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time.

## Importing (Static Composition)

The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). An optional `prefix` can be provided to avoid naming conflicts. If no prefix is provided, components are imported without modification. When multiple servers are imported with the same prefix (or no prefix), the most recently imported server's components take precedence.

```python
from fastmcp import FastMCP
import asyncio

# Define subservers
weather_mcp = FastMCP(name="WeatherService")

@weather_mcp.tool
def get_forecast(city: str) -> dict:
    """Get weather forecast."""
    return {"city": city, "forecast": "Sunny"}

@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
    """List cities with weather support."""
    return ["London", "Paris", "Tokyo"]

# Define main server
main_mcp = FastMCP(name="MainApp")

# Import subserver
async def setup():
    await main_mcp.import_server(weather_mcp, prefix="weather")

# Result: main_mcp now contains prefixed components:
# - Tool: "weather_get_forecast"
# - Resource: "data://weather/cities/supported" 

if __name__ == "__main__":
    asyncio.run(setup())
    main_mcp.run()
```

### How Importing Works

When you call `await main_mcp.import_server(subserver, prefix={whatever})`:

1.  **Tools**: All tools from `subserver` are added to `main_mcp` with names prefixed using `{prefix}_`.
    -   `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`.
2.  **Resources**: All resources are added with URIs prefixed in the format `protocol://{prefix}/path`.
    -   `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="data://{prefix}/info")`.
3.  **Resource Templates**: Templates are prefixed similarly to resources.
    -   `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="data://{prefix}/{id}")`.
4.  **Prompts**: All prompts are added with names prefixed using `{prefix}_`.
    -   `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`.

Note that `import_server` performs a **one-time copy** of components. Changes made to the `subserver` *after* importing **will not** be reflected in `main_mcp`. The `subserver`'s `lifespan` context is also **not** executed by the main server.

<Tip>
The `prefix` parameter is optional. If omitted, components are imported without modification.
</Tip>

#### Importing Without Prefixes

<VersionBadge version="2.9.0" />

You can also import servers without specifying a prefix, which copies components using their original names:

```python

from fastmcp import FastMCP
import asyncio

# Define subservers
weather_mcp = FastMCP(name="WeatherService")

@weather_mcp.tool
def get_forecast(city: str) -> dict:
    """Get weather forecast."""
    return {"city": city, "forecast": "Sunny"}

@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
    """List cities with weather support."""
    return ["London", "Paris", "Tokyo"]

# Define main server
main_mcp = FastMCP(name="MainApp")

# Import subserver
async def setup():
    # Import without prefix - components keep original names
    await main_mcp.import_server(weather_mcp)

# Result: main_mcp now contains:
# - Tool: "get_forecast" (original name preserved)
# - Resource: "data://cities/supported" (original URI preserved)

if __name__ == "__main__":
    asyncio.run(setup())
    main_mcp.run()
```

#### Conflict Resolution

<VersionBadge version="2.9.0" />

When importing multiple servers with the same prefix, or no prefix, components from the **most recently imported** server take precedence.




## Mounting (Live Linking)

The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the optional `prefix` are **delegated** to the `subserver` at runtime. If no prefix is provided, the subserver's components are accessible without prefixing. When multiple servers are mounted with the same prefix (or no prefix), the most recently mounted server takes precedence for conflicting component names.

```python
import asyncio
from fastmcp import FastMCP, Client

# Define subserver
dynamic_mcp = FastMCP(name="DynamicService")

@dynamic_mcp.tool
def initial_tool():
    """Initial tool demonstration."""
    return "Initial Tool Exists"

# Mount subserver (synchronous operation)
main_mcp = FastMCP(name="MainAppLive")
main_mcp.mount(dynamic_mcp, prefix="dynamic")

# Add a tool AFTER mounting - it will be accessible through main_mcp
@dynamic_mcp.tool
def added_later():
    """Tool added after mounting."""
    return "Tool Added Dynamically!"

# Testing access to mounted tools
async def test_dynamic_mount():
    tools = await main_mcp.get_tools()
    print("Available tools:", list(tools.keys()))
    # Shows: ['dynamic_initial_tool', 'dynamic_added_later']
    
    async with Client(main_mcp) as client:
        result = await client.call_tool("dynamic_added_later")
        print("Result:", result[0].text)
        # Shows: "Tool Added Dynamically!"

if __name__ == "__main__":
    asyncio.run(test_dynamic_mount())
```

### How Mounting Works

When mounting is configured:

1. **Live Link**: The parent server establishes a connection to the mounted server.
2. **Dynamic Updates**: Changes to the mounted server are immediately reflected when accessed through the parent.
3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server.
4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime.

The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts.

<Tip>
    The `prefix` parameter is optional. If omitted, components are mounted without modification.
</Tip>


#### Mounting Without Prefixes

<VersionBadge version="2.9.0" />

You can also mount servers without specifying a prefix, which makes components accessible without prefixing. This works identically to [importing without prefixes](#importing-without-prefixes), including [conflict resolution](#conflict-resolution).




### Direct vs. Proxy Mounting

<VersionBadge version="2.2.7" />

FastMCP supports two mounting modes:

1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory.
   - No client lifecycle events occur on the mounted server
   - The mounted server's lifespan context is not executed
   - Communication is handled through direct method calls
   
2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface.
   - Full client lifecycle events occur on the mounted server
   - The mounted server's lifespan is executed when a client connects
   - Communication happens via an in-memory Client transport

```python
# Direct mounting (default when no custom lifespan)
main_mcp.mount(api_server, prefix="api")

# Proxy mounting (preserves full client lifecycle)
main_mcp.mount(api_server, prefix="api", as_proxy=True)

# Mounting without a prefix (components accessible without prefixing)
main_mcp.mount(api_server)
```

FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter.

#### Interaction with Proxy Servers

When using `FastMCP.as_proxy()` to create a proxy server, mounting that server will always use proxy mounting:

```python
# Create a proxy for a remote server
remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp"))

# Mount the proxy (always uses proxy mounting)
main_server.mount(remote_proxy, prefix="remote")
```



## Resource Prefix Formats

<VersionBadge version="2.4.0" />

When mounting or importing servers, resource URIs are usually prefixed to avoid naming conflicts. FastMCP supports two different formats for resource prefixes:

### Path Format (Default)

In path format, prefixes are added to the path component of the URI:

```
resource://prefix/path/to/resource
```

This is the default format since FastMCP 2.4. This format is recommended because it avoids issues with URI protocol restrictions (like underscores not being allowed in protocol names).

### Protocol Format (Legacy)

In protocol format, prefixes are added as part of the protocol:

```
prefix+resource://path/to/resource
```

This was the default format in FastMCP before 2.4. While still supported, it's not recommended for new code as it can cause problems with prefix names that aren't valid in URI protocols.

### Configuring the Prefix Format

You can configure the prefix format globally in code:

```python
import fastmcp
fastmcp.settings.resource_prefix_format = "protocol" 
```

Or via environment variable:

```bash
FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
```

Or per-server:

```python
from fastmcp import FastMCP

# Create a server that uses legacy protocol format
server = FastMCP("LegacyServer", resource_prefix_format="protocol")

# Create a server that uses new path format
server = FastMCP("NewServer", resource_prefix_format="path")
```

When mounting or importing servers, the prefix format of the parent server is used.

================
File: docs/servers/context.mdx
================
---
title: MCP Context
sidebarTitle: Context
description: Access MCP capabilities like logging, progress, and resources within your MCP objects.
icon: rectangle-code
---
import { VersionBadge } from '/snippets/version-badge.mdx'

When defining FastMCP [tools](/servers/tools), [resources](/servers/resources), resource templates, or [prompts](/servers/prompts), your functions might need to interact with the underlying MCP session or access advanced server capabilities. FastMCP provides the `Context` object for this purpose.

## What Is Context?

The `Context` object provides a clean interface to access MCP features within your functions, including:

- **Logging**: Send debug, info, warning, and error messages back to the client
- **Progress Reporting**: Update the client on the progress of long-running operations
- **Resource Access**: Read data from resources registered with the server
- **LLM Sampling**: Request the client's LLM to generate text based on provided messages
- **User Elicitation**: Request structured input from users during tool execution
- **Request Information**: Access metadata about the current request
- **Server Access**: When needed, access the underlying FastMCP server instance

## Accessing the Context

### Via Dependency Injection

To use the context object within any of your functions, simply add a parameter to your function signature and type-hint it as `Context`. FastMCP will automatically inject the context instance when your function is called.

**Key Points:**

- The parameter name (e.g., `ctx`, `context`) doesn't matter, only the type hint `Context` is important.
- The context parameter can be placed anywhere in your function's signature; it will not be exposed to MCP clients as a valid parameter.
- The context is optional - functions that don't need it can omit the parameter entirely.
- Context methods are async, so your function usually needs to be async as well.
- The type hint can be a union (`Context | None`) or use `Annotated[]` and it will still work properly.
- Context is only available during a request; attempting to use context methods outside a request will raise errors. If you need to debug or call your context methods outside of a request, you can type your variable as `Context | None=None` to avoid missing argument errors.

#### Tools

```python {1, 6}
from fastmcp import FastMCP, Context

mcp = FastMCP(name="Context Demo")

@mcp.tool
async def process_file(file_uri: str, ctx: Context) -> str:
    """Processes a file, using context for logging and resource access."""
    # Context is available as the ctx parameter
    return "Processed file"
```

#### Resources and Templates

<VersionBadge version="2.2.5" />

```python {1, 6, 12}
from fastmcp import FastMCP, Context

mcp = FastMCP(name="Context Demo")

@mcp.resource("resource://user-data")
async def get_user_data(ctx: Context) -> dict:
    """Fetch personalized user data based on the request context."""
    # Context is available as the ctx parameter
    return {"user_id": "example"}

@mcp.resource("resource://users/{user_id}/profile")
async def get_user_profile(user_id: str, ctx: Context) -> dict:
    """Fetch user profile with context-aware logging."""
    # Context is available as the ctx parameter
    return {"id": user_id}
```

#### Prompts

<VersionBadge version="2.2.5" />

```python {1, 6}
from fastmcp import FastMCP, Context

mcp = FastMCP(name="Context Demo")

@mcp.prompt
async def data_analysis_request(dataset: str, ctx: Context) -> str:
    """Generate a request to analyze data with contextual information."""
    # Context is available as the ctx parameter
    return f"Please analyze the following dataset: {dataset}"
```


### Via Dependency Function

<VersionBadge version="2.2.11" />

While the simplest way to access context is through function parameter injection as shown above, there are cases where you need to access the context in code that may not be easy to modify to accept a context parameter, or that is nested deeper within your function calls.

FastMCP provides dependency functions that allow you to retrieve the active context from anywhere within a server request's execution flow:

```python {2,9}
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_context

mcp = FastMCP(name="Dependency Demo")

# Utility function that needs context but doesn't receive it as a parameter
async def process_data(data: list[float]) -> dict:
    # Get the active context - only works when called within a request
    ctx = get_context()    
    await ctx.info(f"Processing {len(data)} data points")
    
@mcp.tool
async def analyze_dataset(dataset_name: str) -> dict:
    # Call utility function that uses context internally
    data = load_data(dataset_name)
    await process_data(data)
```

**Important Notes:**

- The `get_context` function should only be used within the context of a server request. Calling it outside of a request will raise a `RuntimeError`.
- The `get_context` function is server-only and should not be used in client code.

## Context Capabilities

FastMCP provides several advanced capabilities through the context object. Each capability has dedicated documentation with comprehensive examples and best practices:

### Logging

Send debug, info, warning, and error messages back to the MCP client for visibility into function execution.

```python
await ctx.debug("Starting analysis")
await ctx.info(f"Processing {len(data)} items") 
await ctx.warning("Deprecated parameter used")
await ctx.error("Processing failed")
```

See [Server Logging](/servers/logging) for complete documentation and examples.
### Client Elicitation

<VersionBadge version="2.10.0" />

Request structured input from clients during tool execution, enabling interactive workflows and progressive disclosure. This is a new feature in the 6/18/2025 MCP spec.

```python
result = await ctx.elicit("Enter your name:", response_type=str)
if result.action == "accept":
    name = result.data
```

See [User Elicitation](/servers/elicitation) for detailed examples and supported response types.

### LLM Sampling

<VersionBadge version="2.0.0" />

Request the client's LLM to generate text based on provided messages, useful for leveraging AI capabilities within your tools.

```python
response = await ctx.sample("Analyze this data", temperature=0.7)
```

See [LLM Sampling](/servers/sampling) for comprehensive usage and advanced techniques.


### Progress Reporting

Update clients on the progress of long-running operations, enabling progress indicators and better user experience.

```python
await ctx.report_progress(progress=50, total=100)  # 50% complete
```

See [Progress Reporting](/servers/progress) for detailed patterns and examples.

### Resource Access

Read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content.

```python
content_list = await ctx.read_resource("resource://config")
content = content_list[0].content
```

**Method signature:**
- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts


### Change Notifications

<VersionBadge version="2.9.1" />

FastMCP automatically sends list change notifications when components (such as tools, resources, or prompts) are added, removed, enabled, or disabled. In rare cases where you need to manually trigger these notifications, you can use the context methods:

```python
@mcp.tool
async def custom_tool_management(ctx: Context) -> str:
    """Example of manual notification after custom tool changes."""
    # After making custom changes to tools
    await ctx.send_tool_list_changed()
    await ctx.send_resource_list_changed()
    await ctx.send_prompt_list_changed()
    return "Notifications sent"
```

These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.

### FastMCP Server

To access the underlying FastMCP server instance, you can use the `ctx.fastmcp` property:

```python
@mcp.tool
async def my_tool(ctx: Context) -> None:
    # Access the FastMCP server instance
    server_name = ctx.fastmcp.name
    ...
```

### MCP Request

Access metadata about the current request and client.

```python
@mcp.tool
async def request_info(ctx: Context) -> dict:
    """Return information about the current request."""
    return {
        "request_id": ctx.request_id,
        "client_id": ctx.client_id or "Unknown client"
    }
```

**Available Properties:**

- **`ctx.request_id -> str`**: Get the unique ID for the current MCP request
- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only)

<Warning>
The MCP request is part of the low-level MCP SDK and intended for advanced use cases. Most users will not need to use it directly.
</Warning>

================
File: docs/servers/elicitation.mdx
================
---
title: User Elicitation
sidebarTitle: Elicitation
description: Request structured input from users during tool execution through the MCP context.
icon: message-question
tag: NEW
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.10.0" />

User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed.

<Tip>
Most of the examples in this document assume you have a FastMCP server instance named `mcp` and show how to use the `ctx.elicit` method to request user input from an `@mcp.tool`-decorated function.
</Tip>

## What is Elicitation?

Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for:

- **Missing parameters**: Ask for required information not provided initially
- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios  
- **Progressive disclosure**: Collect complex information step-by-step
- **Dynamic workflows**: Adapt tool behavior based on user responses

For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"

### Basic Usage

Use the `ctx.elicit()` method within any tool function to request user input:

```python {14-17}
from fastmcp import FastMCP, Context
from dataclasses import dataclass

mcp = FastMCP("Elicitation Server")

@dataclass
class UserInfo:
    name: str
    age: int

@mcp.tool
async def collect_user_info(ctx: Context) -> str:
    """Collect user information through interactive prompts."""
    result = await ctx.elicit(
        message="Please provide your information",
        response_type=UserInfo
    )
    
    if result.action == "accept":
        user = result.data
        return f"Hello {user.name}, you are {user.age} years old"
    elif result.action == "decline":
        return "Information not provided"
    else:  # cancel
        return "Operation cancelled"
```

## Method Signature

<Card icon="code" title="Context Elicitation Method">
<ResponseField name="ctx.elicit" type="async method">
  <Expandable title="Parameters">
    <ResponseField name="message" type="str">
      The prompt message to display to the user
    </ResponseField>
    
    <ResponseField name="response_type" type="type" default="str">
      The Python type defining the expected response structure (dataclass, primitive type, etc.) Note that elicitation responses are subject to a restricted subset of JSON Schema types. See [Supported Response Types](#supported-response-types) for more details.
    </ResponseField>
  </Expandable>
  
  <Expandable title="Response">
    <ResponseField name="ElicitationResult" type="object">
      Result object containing the user's response
      
      <Expandable title="properties">
        <ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
          How the user responded to the request
        </ResponseField>
        
        <ResponseField name="data" type="response_type | None">
          The user's input data (only present when action is "accept")
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

## Elicitation Actions

The elicitation result contains an `action` field indicating how the user responded:

- **`accept`**: User provided valid input - data is available in the `data` field
- **`decline`**: User chose not to provide the requested information and the data field is `None`
- **`cancel`**: User cancelled the entire operation and the data field is `None`

```python {5, 7}
@mcp.tool
async def my_tool(ctx: Context) -> str:
    result = await ctx.elicit("Choose an action")

    if result.action == "accept":
        return "Accepted!"
    elif result.action == "decline":
        return "Declined!"
    else:
        return "Cancelled!"
```

FastMCP also provides typed result classes for pattern matching on the `action` field:

```python {1-5, 12, 14, 16}
from fastmcp.server.elicitation import (
    AcceptedElicitation, 
    DeclinedElicitation, 
    CancelledElicitation,
)

@mcp.tool
async def pattern_example(ctx: Context) -> str:
    result = await ctx.elicit("Enter your name:", response_type=str)
    
    match result:
        case AcceptedElicitation(data=name):
            return f"Hello {name}!"
        case DeclinedElicitation():
            return "No name provided"
        case CancelledElicitation():
            return "Operation cancelled"
```

## Response Types

The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is `accept`-ed, the client must send a response that matches the schema.

The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON  **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean` and `enum` fields.

FastMCP makes it easy to request a broader range of types, including scalars (e.g. `str`), by automatically wrapping them in MCP-compatible object schemas.


### Scalar Types

You can request simple scalar data types for basic input, such as a string, integer, or boolean.

When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned to your tool function as the `data` field of the `ElicitationResult` object.

As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value.

<CodeGroup>
```python {4} title="Request a string"
@mcp.tool
async def get_user_name(ctx: Context) -> str:
    """Get the user's name."""
    result = await ctx.elicit("What's your name?", response_type=str)
    
    if result.action == "accept":
        return f"Hello, {result.data}!"
    return "No name provided"
```
```python {4} title="Request an integer"
@mcp.tool
async def pick_a_number(ctx: Context) -> str:
    """Pick a number."""
    result = await ctx.elicit("Pick a number!", response_type=int)
    
    if result.action == "accept":
        return f"You picked {result.data}"
    return "No number provided"
```
```python {4} title="Request a boolean"
@mcp.tool
async def pick_a_boolean(ctx: Context) -> str:
    """Pick a boolean."""
    result = await ctx.elicit("True or false?", response_type=bool)
    
    if result.action == "accept":
        return f"You picked {result.data}"
    return "No boolean provided"
```
</CodeGroup>

### Constrained Options

Often you'll want to constrain the user's response to a specific set of values. You can do this by using a `Literal` type or a Python enum as the response type, or by passing a list of strings to the `response_type` parameter as a convenient shortcut.

<CodeGroup>
```python {6} title="Using a list of strings"
@mcp.tool
async def set_priority(ctx: Context) -> str:
    """Set task priority level."""
    result = await ctx.elicit(
        "What priority level?", 
        response_type=["low", "medium", "high"],
    )
    
    if result.action == "accept":
        return f"Priority set to: {result.data}"
```
```python {1, 8} title="Using a Literal type"
from typing import Literal

@mcp.tool
async def set_priority(ctx: Context) -> str:
    """Set task priority level."""
    result = await ctx.elicit(
        "What priority level?", 
        response_type=Literal["low", "medium", "high"]
    )
    
    if result.action == "accept":
        return f"Priority set to: {result.data}"
    return "No priority set"
```
```python {1, 11} title="Using a Python enum"
from enum import Enum

class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"   

@mcp.tool
async def set_priority(ctx: Context) -> str:
    """Set task priority level."""
    result = await ctx.elicit("What priority level?", response_type=Priority)
    
    if result.action == "accept":
        return f"Priority set to: {result.data.value}"
    return "No priority set"
```
</CodeGroup>


### Structured Responses

You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties.

```python {1, 16, 20}
from dataclasses import dataclass
from typing import Literal

@dataclass
class TaskDetails:
    title: str
    description: str
    priority: Literal["low", "medium", "high"]
    due_date: str

@mcp.tool
async def create_task(ctx: Context) -> str:
    """Create a new task with user-provided details."""
    result = await ctx.elicit(
        "Please provide task details",
        response_type=TaskDetails
    )
    
    if result.action == "accept":
        task = result.data
        return f"Created task: {task.title} (Priority: {task.priority})"
    return "Task creation cancelled"
```

## Multi-Turn Elicitation

Tools can make multiple elicitation calls to gather information progressively:

```python {6, 11, 16-19}
@mcp.tool
async def plan_meeting(ctx: Context) -> str:
    """Plan a meeting by gathering details step by step."""
    
    # Get meeting title
    title_result = await ctx.elicit("What's the meeting title?", response_type=str)
    if title_result.action != "accept":
        return "Meeting planning cancelled"
    
    # Get duration
    duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
    if duration_result.action != "accept":
        return "Meeting planning cancelled"
    
    # Get priority
    priority_result = await ctx.elicit(
        "Is this urgent?", 
        response_type=Literal["yes", "no"]
    )
    if priority_result.action != "accept":
        return "Meeting planning cancelled"
    
    urgent = priority_result.data == "yes"
    return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})"
```


## Client Requirements

Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests.

If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported.

================
File: docs/servers/logging.mdx
================
---
title: Server Logging
sidebarTitle: Logging
description: Send log messages back to MCP clients through the context.
icon: receipt
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<Tip>
This documentation covers **MCP client logging** - sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module.
</Tip>

Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. This provides visibility into function execution and helps with debugging during development and operation.

## Why Use Server Logging?

Server logging is essential for:

- **Debugging**: Send detailed execution information to help diagnose issues
- **Progress visibility**: Keep users informed about what the tool is doing
- **Error reporting**: Communicate problems and their context to clients
- **Audit trails**: Create records of tool execution for compliance or analysis

Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs.

### Basic Usage

Use the context logging methods within any tool function:

```python {8-9, 13, 17, 21}
from fastmcp import FastMCP, Context

mcp = FastMCP("LoggingDemo")

@mcp.tool
async def analyze_data(data: list[float], ctx: Context) -> dict:
    """Analyze numerical data with comprehensive logging."""
    await ctx.debug("Starting analysis of numerical data")
    await ctx.info(f"Analyzing {len(data)} data points")
    
    try:
        if not data:
            await ctx.warning("Empty data list provided")
            return {"error": "Empty data list"}
        
        result = sum(data) / len(data)
        await ctx.info(f"Analysis complete, average: {result}")
        return {"average": result, "count": len(data)}
        
    except Exception as e:
        await ctx.error(f"Analysis failed: {str(e)}")
        raise
```

## Logging Methods

<Card icon="code" title="Context Logging Methods">
<ResponseField name="ctx.debug" type="async method">
  Send debug-level messages for detailed execution information
  
  <Expandable title="parameters">
    <ResponseField name="message" type="str">
      The debug message to send to the client
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="ctx.info" type="async method">
  Send informational messages about normal execution
  
  <Expandable title="parameters">
    <ResponseField name="message" type="str">
      The information message to send to the client
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="ctx.warning" type="async method">
  Send warning messages for potential issues that didn't prevent execution
  
  <Expandable title="parameters">
    <ResponseField name="message" type="str">
      The warning message to send to the client
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="ctx.error" type="async method">
  Send error messages for problems that occurred during execution
  
  <Expandable title="parameters">
    <ResponseField name="message" type="str">
      The error message to send to the client
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="ctx.log" type="async method">
  Generic logging method with custom level and logger name
  
  <Expandable title="parameters">
    <ResponseField name="level" type="Literal['debug', 'info', 'warning', 'error']">
      The log level for the message
    </ResponseField>
    
    <ResponseField name="message" type="str">
      The message to send to the client
    </ResponseField>
    
    <ResponseField name="logger_name" type="str | None" default="None">
      Optional custom logger name for categorizing messages
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

## Log Levels

### Debug
Use for detailed information that's typically only useful when diagnosing problems:

```python 
@mcp.tool
async def process_file(file_path: str, ctx: Context) -> str:
    """Process a file with detailed debug logging."""
    await ctx.debug(f"Starting to process file: {file_path}")
    await ctx.debug("Checking file permissions")
    
    # File processing logic
    await ctx.debug("File processing completed successfully")
    return "File processed"
```

### Info
Use for general information about normal program execution:

```python
@mcp.tool
async def backup_database(ctx: Context) -> str:
    """Backup database with progress information."""
    await ctx.info("Starting database backup")
    await ctx.info("Connecting to database")
    await ctx.info("Backup completed successfully")
    return "Database backed up"
```

### Warning
Use for potentially harmful situations that don't prevent execution:

```python
@mcp.tool
async def validate_config(config: dict, ctx: Context) -> dict:
    """Validate configuration with warnings for deprecated options."""
    if "old_api_key" in config:
        await ctx.warning("Using deprecated 'old_api_key' field. Please use 'api_key' instead")
    
    if config.get("timeout", 30) > 300:
        await ctx.warning("Timeout value is very high (>5 minutes), this may cause issues")
    
    return {"status": "valid", "warnings": "see logs"}
```

### Error
Use for error events that might still allow the application to continue:

```python
@mcp.tool
async def batch_process(items: list[str], ctx: Context) -> dict:
    """Process multiple items, logging errors for failed items."""
    successful = 0
    failed = 0
    
    for item in items:
        try:
            # Process item
            successful += 1
        except Exception as e:
            await ctx.error(f"Failed to process item '{item}': {str(e)}")
            failed += 1
    
    return {"successful": successful, "failed": failed}
```


## Client Handling

Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation:

- **Development clients**: May display logs in real-time for debugging
- **Production clients**: May store logs for later analysis or display to users
- **Integration clients**: May forward logs to external logging systems

See [Client Logging](/clients/logging) for details on how clients can handle server log messages.

================
File: docs/servers/middleware.mdx
================
---
title: MCP Middleware
sidebarTitle: Middleware
description: Add cross-cutting functionality to your MCP server with middleware that can inspect, modify, and respond to all MCP requests and responses.
icon: layer-group
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.9.0" />

MCP middleware is a powerful concept that allows you to add cross-cutting functionality to your FastMCP server. Unlike traditional web middleware, MCP middleware is designed specifically for the Model Context Protocol, providing hooks for different types of MCP operations like tool calls, resource reads, and prompt requests.

<Tip>
MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. This middleware system is designed to work with FastMCP servers and may not be compatible with other MCP implementations.
</Tip>

<Warning>
MCP middleware is a brand new concept and may be subject to breaking changes in future versions.
</Warning>

## What is MCP Middleware?

MCP middleware lets you intercept and modify MCP requests and responses as they flow through your server. Think of it as a pipeline where each piece of middleware can inspect what's happening, make changes, and then pass control to the next middleware in the chain.

Common use cases for MCP middleware include:
- **Authentication and Authorization**: Verify client permissions before executing operations
- **Logging and Monitoring**: Track usage patterns and performance metrics
- **Rate Limiting**: Control request frequency per client or operation type
- **Request/Response Transformation**: Modify data before it reaches tools or after it leaves
- **Caching**: Store frequently requested data to improve performance
- **Error Handling**: Provide consistent error responses across your server

## How Middleware Works

FastMCP middleware operates on a pipeline model. When a request comes in, it flows through your middleware in the order they were added to the server. Each middleware can:

1. **Inspect the incoming request** and its context
2. **Modify the request** before passing it to the next middleware or handler
3. **Execute the next middleware/handler** in the chain by calling `call_next()`
4. **Inspect and modify the response** before returning it
5. **Handle errors** that occur during processing

The key insight is that middleware forms a chain where each piece decides whether to continue processing or stop the chain entirely.

If you're familiar with ASGI middleware, the basic structure of FastMCP middleware will feel familiar. At its core, middleware is a callable class that receives a context object containing information about the current JSON-RPC message and a handler function to continue the middleware chain.

It's important to understand that MCP operates on the [JSON-RPC specification](https://spec.modelcontextprotocol.io/specification/basic/transports/). While FastMCP presents requests and responses in a familiar way, these are fundamentally JSON-RPC messages, not HTTP request/response pairs like you might be used to in web applications. FastMCP middleware works with all [transport types](/clients/transports), including local stdio transport and HTTP transports, though not all middleware implementations are compatible across all transports (e.g., middleware that inspects HTTP headers won't work with stdio transport).

The most fundamental way to implement middleware is by overriding the `__call__` method on the `Middleware` base class:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext

class RawMiddleware(Middleware):
    async def __call__(self, context: MiddlewareContext, call_next):
        # This method receives ALL messages regardless of type
        print(f"Raw middleware processing: {context.method}")
        result = await call_next(context)
        print(f"Raw middleware completed: {context.method}")
        return result
```

This gives you complete control over every message that flows through your server, but requires you to handle all message types manually.

## Middleware Hooks

To make it easier for users to target specific types of messages, FastMCP middleware provides a variety of specialized hooks. Instead of implementing the raw `__call__` method, you can override specific hook methods that are called only for certain types of operations, allowing you to target exactly the level of specificity you need for your middleware logic.

### Hook Hierarchy and Execution Order

FastMCP provides multiple hooks that are called with varying levels of specificity. Understanding this hierarchy is crucial for effective middleware design.

When a request comes in, **multiple hooks may be called for the same request**, going from general to specific:

1. **`on_message`** - Called for ALL MCP messages (both requests and notifications)
2. **`on_request` or `on_notification`** - Called based on the message type
3. **Operation-specific hooks** - Called for specific MCP operations like `on_call_tool`

For example, when a client calls a tool, your middleware will receive **multiple hook calls**:
1. `on_message` and `on_request` for any initial tool discovery operations (list_tools)
2. `on_message` (because it's any MCP message) for the tool call itself
3. `on_request` (because tool calls expect responses) for the tool call itself
4. `on_call_tool` (because it's specifically a tool execution) for the tool call itself

Note that the MCP SDK may perform additional operations like listing tools for caching purposes, which will trigger additional middleware calls beyond just the direct tool execution.

This hierarchy allows you to target your middleware logic with the right level of specificity. Use `on_message` for broad concerns like logging, `on_request` for authentication, and `on_call_tool` for tool-specific logic like performance monitoring.

### Available Hooks

- `on_message`: Called for all MCP messages (requests and notifications)
- `on_request`: Called specifically for MCP requests (that expect responses)
- `on_notification`: Called specifically for MCP notifications (fire-and-forget)
- `on_call_tool`: Called when tools are being executed
- `on_read_resource`: Called when resources are being read
- `on_get_prompt`: Called when prompts are being retrieved
- `on_list_tools`: Called when listing available tools
- `on_list_resources`: Called when listing available resources
- `on_list_resource_templates`: Called when listing resource templates
- `on_list_prompts`: Called when listing available prompts

## Component Access in Middleware

Understanding how to access component information (tools, resources, prompts) in middleware is crucial for building powerful middleware functionality. The access patterns differ significantly between listing operations and execution operations.

### Listing Operations vs Execution Operations

FastMCP middleware handles two types of operations differently:

**Listing Operations** (`on_list_tools`, `on_list_resources`, `on_list_prompts`, etc.):
- Middleware receives **FastMCP component objects** with full metadata
- These objects include FastMCP-specific properties like `tags` that aren't part of the MCP specification
- The result contains complete component information before it's converted to MCP format
- Tags and other metadata are stripped when finally returned to the MCP client

**Execution Operations** (`on_call_tool`, `on_read_resource`, `on_get_prompt`):
- Middleware runs **before** the component is executed
- The middleware result is either the execution result or an error if the component wasn't found
- Component metadata isn't directly available in the hook parameters

### Accessing Component Metadata During Execution

If you need to check component properties (like tags) during execution operations, use the FastMCP server instance available through the context:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.exceptions import ToolError

class TagBasedMiddleware(Middleware):
    async def on_call_tool(self, context: MiddlewareContext, call_next):
        # Access the tool object to check its metadata
        if context.fastmcp_context:
            try:
                tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name)
                
                # Check if this tool has a "private" tag
                if "private" in tool.tags:
                    raise ToolError("Access denied: private tool")
                    
                # Check if tool is enabled
                if not tool.enabled:
                    raise ToolError("Tool is currently disabled")
                    
            except Exception:
                # Tool not found or other error - let execution continue
                # and handle the error naturally
                pass
        
        return await call_next(context)
```

The same pattern works for resources and prompts:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.exceptions import ResourceError, PromptError

class ComponentAccessMiddleware(Middleware):
    async def on_read_resource(self, context: MiddlewareContext, call_next):
        if context.fastmcp_context:
            try:
                resource = await context.fastmcp_context.fastmcp.get_resource(context.message.uri)
                if "restricted" in resource.tags:
                    raise ResourceError("Access denied: restricted resource")
            except Exception:
                pass
        return await call_next(context)
    
    async def on_get_prompt(self, context: MiddlewareContext, call_next):
        if context.fastmcp_context:
            try:
                prompt = await context.fastmcp_context.fastmcp.get_prompt(context.message.name)
                if not prompt.enabled:
                    raise PromptError("Prompt is currently disabled")
            except Exception:
                pass
        return await call_next(context)
```

### Working with Listing Results

For listing operations, you can inspect and modify the FastMCP components directly:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext, ListToolsResult

class ListingFilterMiddleware(Middleware):
    async def on_list_tools(self, context: MiddlewareContext, call_next):
        result = await call_next(context)
        
        # Filter out tools with "private" tag
        filtered_tools = {
            name: tool for name, tool in result.tools.items()
            if "private" not in tool.tags
        }
        
        # Return modified result
        return ListToolsResult(tools=filtered_tools)
```

This filtering happens before the components are converted to MCP format and returned to the client, so the tags (which are FastMCP-specific) are naturally stripped in the final response.

### Anatomy of a Hook

Every middleware hook follows the same pattern. Let's examine the `on_message` hook to understand the structure:

```python
async def on_message(self, context: MiddlewareContext, call_next):
    # 1. Pre-processing: Inspect and optionally modify the request
    print(f"Processing {context.method}")
    
    # 2. Chain continuation: Call the next middleware/handler
    result = await call_next(context)
    
    # 3. Post-processing: Inspect and optionally modify the response
    print(f"Completed {context.method}")
    
    # 4. Return the result (potentially modified)
    return result
```

### Hook Parameters

Every hook receives two parameters:

1. **`context: MiddlewareContext`** - Contains information about the current request:
   - `context.method` - The MCP method name (e.g., "tools/call")
   - `context.source` - Where the request came from ("client" or "server")
   - `context.type` - Message type ("request" or "notification")
   - `context.message` - The MCP message data
   - `context.timestamp` - When the request was received
   - `context.fastmcp_context` - FastMCP Context object (if available)

2. **`call_next`** - A function that continues the middleware chain. You **must** call this to proceed, unless you want to stop processing entirely.

### Control Flow

You have complete control over the request flow:
- **Continue processing**: Call `await call_next(context)` to proceed
- **Modify the request**: Change the context before calling `call_next`
- **Modify the response**: Change the result after calling `call_next`
- **Stop the chain**: Don't call `call_next` (rarely needed)
- **Handle errors**: Wrap `call_next` in try/catch blocks

## Creating Middleware

FastMCP middleware is implemented by subclassing the `Middleware` base class and overriding the hooks you need. You only need to implement the hooks that are relevant to your use case.

```python
from fastmcp import FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContext

class LoggingMiddleware(Middleware):
    """Middleware that logs all MCP operations."""
    
    async def on_message(self, context: MiddlewareContext, call_next):
        """Called for all MCP messages."""
        print(f"Processing {context.method} from {context.source}")
        
        result = await call_next(context)
        
        print(f"Completed {context.method}")
        return result

# Add middleware to your server
mcp = FastMCP("MyServer")
mcp.add_middleware(LoggingMiddleware())
```

This creates a basic logging middleware that will print information about every request that flows through your server.

## Adding Middleware to Your Server

### Single Middleware

Adding middleware to your server is straightforward:

```python
mcp = FastMCP("MyServer")
mcp.add_middleware(LoggingMiddleware())
```

### Multiple Middleware

Middleware executes in the order it's added to the server. The first middleware added runs first on the way in, and last on the way out:

```python
mcp = FastMCP("MyServer")

mcp.add_middleware(AuthenticationMiddleware("secret-token"))
mcp.add_middleware(PerformanceMiddleware())
mcp.add_middleware(LoggingMiddleware())
```

This creates the following execution flow:
1. AuthenticationMiddleware (pre-processing)
2. PerformanceMiddleware (pre-processing)  
3. LoggingMiddleware (pre-processing)
4. Actual tool/resource handler
5. LoggingMiddleware (post-processing)
6. PerformanceMiddleware (post-processing)
7. AuthenticationMiddleware (post-processing)

## Server Composition and Middleware

When using [Server Composition](/servers/composition) with `mount` or `import_server`, middleware behavior follows these rules:

1. **Parent server middleware** runs for all requests, including those routed to mounted servers
2. **Mounted server middleware** only runs for requests handled by that specific server
3. **Middleware order** is preserved within each server

This allows you to create layered middleware architectures where parent servers handle cross-cutting concerns like authentication, while child servers focus on domain-specific middleware.

```python
# Parent server with middleware
parent = FastMCP("Parent")
parent.add_middleware(AuthenticationMiddleware("token"))

# Child server with its own middleware  
child = FastMCP("Child")
child.add_middleware(LoggingMiddleware())

@child.tool
def child_tool() -> str:
    return "from child"

# Mount the child server
parent.mount(child, prefix="child")
```

When a client calls "child_tool", the request will flow through the parent's authentication middleware first, then route to the child server where it will go through the child's logging middleware.

## Built-in Middleware Examples

FastMCP includes several middleware implementations that demonstrate best practices and provide immediately useful functionality. Let's explore how each type works by building simplified versions, then see how to use the full implementations.

### Timing Middleware

Performance monitoring is essential for understanding your server's behavior and identifying bottlenecks. FastMCP includes timing middleware at `fastmcp.server.middleware.timing`. 

Here's an example of how it works:

```python
import time
from fastmcp.server.middleware import Middleware, MiddlewareContext

class SimpleTimingMiddleware(Middleware):
    async def on_request(self, context: MiddlewareContext, call_next):
        start_time = time.perf_counter()
        
        try:
            result = await call_next(context)
            duration_ms = (time.perf_counter() - start_time) * 1000
            print(f"Request {context.method} completed in {duration_ms:.2f}ms")
            return result
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            print(f"Request {context.method} failed after {duration_ms:.2f}ms: {e}")
            raise
```

To use the full version with proper logging and configuration:

```python
from fastmcp.server.middleware.timing import (
    TimingMiddleware, 
    DetailedTimingMiddleware
)

# Basic timing for all requests
mcp.add_middleware(TimingMiddleware())

# Detailed per-operation timing (tools, resources, prompts)
mcp.add_middleware(DetailedTimingMiddleware())
```

The built-in versions include custom logger support, proper formatting, and **DetailedTimingMiddleware** provides operation-specific hooks like `on_call_tool` and `on_read_resource` for granular timing.

### Logging Middleware

Request and response logging is crucial for debugging, monitoring, and understanding usage patterns in your MCP server. FastMCP provides comprehensive logging middleware at `fastmcp.server.middleware.logging`. 

Here's an example of how it works:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext

class SimpleLoggingMiddleware(Middleware):
    async def on_message(self, context: MiddlewareContext, call_next):
        print(f"Processing {context.method} from {context.source}")
        
        try:
            result = await call_next(context)
            print(f"Completed {context.method}")
            return result
        except Exception as e:
            print(f"Failed {context.method}: {e}")
            raise
```

To use the full versions with advanced features:

```python
from fastmcp.server.middleware.logging import (
    LoggingMiddleware, 
    StructuredLoggingMiddleware
)

# Human-readable logging with payload support
mcp.add_middleware(LoggingMiddleware(
    include_payloads=True,
    max_payload_length=1000
))

# JSON-structured logging for log aggregation tools
mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True))
```

The built-in versions include payload logging, structured JSON output, custom logger support, payload size limits, and operation-specific hooks for granular control.

### Rate Limiting Middleware

Rate limiting is essential for protecting your server from abuse, ensuring fair resource usage, and maintaining performance under load. FastMCP includes sophisticated rate limiting middleware at `fastmcp.server.middleware.rate_limiting`. 

Here's an example of how it works:

```python
import time
from collections import defaultdict
from fastmcp.server.middleware import Middleware, MiddlewareContext
from mcp import McpError
from mcp.types import ErrorData

class SimpleRateLimitMiddleware(Middleware):
    def __init__(self, requests_per_minute: int = 60):
        self.requests_per_minute = requests_per_minute
        self.client_requests = defaultdict(list)
    
    async def on_request(self, context: MiddlewareContext, call_next):
        current_time = time.time()
        client_id = "default"  # In practice, extract from headers or context
        
        # Clean old requests and check limit
        cutoff_time = current_time - 60
        self.client_requests[client_id] = [
            req_time for req_time in self.client_requests[client_id]
            if req_time > cutoff_time
        ]
        
        if len(self.client_requests[client_id]) >= self.requests_per_minute:
            raise McpError(ErrorData(code=-32000, message="Rate limit exceeded"))
        
        self.client_requests[client_id].append(current_time)
        return await call_next(context)
```

To use the full versions with advanced algorithms:

```python
from fastmcp.server.middleware.rate_limiting import (
    RateLimitingMiddleware, 
    SlidingWindowRateLimitingMiddleware
)

# Token bucket rate limiting (allows controlled bursts)
mcp.add_middleware(RateLimitingMiddleware(
    max_requests_per_second=10.0,
    burst_capacity=20
))

# Sliding window rate limiting (precise time-based control)
mcp.add_middleware(SlidingWindowRateLimitingMiddleware(
    max_requests=100,
    window_minutes=1
))
```

The built-in versions include token bucket algorithms, per-client identification, global rate limiting, and async-safe implementations with configurable client identification functions.

### Error Handling Middleware

Consistent error handling and recovery is critical for robust MCP servers. FastMCP provides comprehensive error handling middleware at `fastmcp.server.middleware.error_handling`.

Here's an example of how it works:

```python
import logging
from fastmcp.server.middleware import Middleware, MiddlewareContext

class SimpleErrorHandlingMiddleware(Middleware):
    def __init__(self):
        self.logger = logging.getLogger("errors")
        self.error_counts = {}
    
    async def on_message(self, context: MiddlewareContext, call_next):
        try:
            return await call_next(context)
        except Exception as error:
            # Log the error and track statistics
            error_key = f"{type(error).__name__}:{context.method}"
            self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1
            
            self.logger.error(f"Error in {context.method}: {type(error).__name__}: {error}")
            raise
```

To use the full versions with advanced features:

```python
from fastmcp.server.middleware.error_handling import (
    ErrorHandlingMiddleware, 
    RetryMiddleware
)

# Comprehensive error logging and transformation
mcp.add_middleware(ErrorHandlingMiddleware(
    include_traceback=True,
    transform_errors=True,
    error_callback=my_error_callback
))

# Automatic retry with exponential backoff
mcp.add_middleware(RetryMiddleware(
    max_retries=3,
    retry_exceptions=(ConnectionError, TimeoutError)
))
```

The built-in versions include error transformation, custom callbacks, configurable retry logic, and proper MCP error formatting.

### Combining Middleware

These middleware work together seamlessly:

```python
from fastmcp import FastMCP
from fastmcp.server.middleware.timing import TimingMiddleware
from fastmcp.server.middleware.logging import LoggingMiddleware
from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware
from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware

mcp = FastMCP("Production Server")

# Add middleware in logical order
mcp.add_middleware(ErrorHandlingMiddleware())  # Handle errors first
mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=50))
mcp.add_middleware(TimingMiddleware())  # Time actual execution
mcp.add_middleware(LoggingMiddleware())  # Log everything

@mcp.tool
def my_tool(data: str) -> str:
    return f"Processed: {data}"
```

This configuration provides comprehensive monitoring, protection, and observability for your MCP server.

### Custom Middleware Example

You can also create custom middleware by extending the base class:

```python
from fastmcp.server.middleware import Middleware, MiddlewareContext

class CustomHeaderMiddleware(Middleware):
    async def on_request(self, context: MiddlewareContext, call_next):
        # Add custom logic here
        print(f"Processing {context.method}")
        
        result = await call_next(context)
        
        print(f"Completed {context.method}")
        return result

mcp.add_middleware(CustomHeaderMiddleware())
```

================
File: docs/servers/openapi.mdx
================
---
title: OpenAPI Integration
sidebarTitle: OpenAPI Integration
description: Generate MCP servers from OpenAPI specs and FastAPI apps
icon: code-branch
---
import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

FastMCP can automatically generate an MCP server from an OpenAPI specification or FastAPI app. Instead of manually creating tools and resources, you provide an OpenAPI spec and FastMCP intelligently converts your API endpoints into the appropriate MCP components.

## Quick Start

To convert an OpenAPI specification to an MCP server, you can use the `FastMCP.from_openapi` class method. This method takes an OpenAPI specification and an async HTTPX client that can be used to make requests to the API, and returns an MCP server.

Here's an example:
```python {11-15}
import httpx
from fastmcp import FastMCP

# Create an HTTP client for your API
client = httpx.AsyncClient(base_url="https://api.example.com")

# Load your OpenAPI spec 
openapi_spec = httpx.get("https://api.example.com/openapi.json").json()

# Create the MCP server
mcp = FastMCP.from_openapi(
    openapi_spec=openapi_spec,
    client=client,
    name="My API Server"
)

if __name__ == "__main__":
    mcp.run()
```

That's it! Your entire API is now available as an MCP server. Clients can discover and interact with your API endpoints through the MCP protocol, with full schema validation and type safety.


## Route Mapping

By default, FastMCP converts **every endpoint** in your OpenAPI specification into an MCP **Tool**. This provides a simple, predictable starting point that ensures all your API's functionality is immediately available to the vast majority of LLM clients which only support MCP tools.

While this is a pragmatic default for maximum compatibility, you can easily customize this behavior. Internally, FastMCP uses an ordered list of `RouteMap` objects to determine how to map OpenAPI routes to various MCP component types.

Each `RouteMap` specifies a combination of methods, patterns, and tags, as well as a corresponding MCP component type. Each OpenAPI route is checked against each `RouteMap` in order, and the first one that matches every criteria is used to determine its converted MCP type. A special type, `EXCLUDE`, can be used to exclude routes from the MCP server entirely.

- **Methods**: HTTP methods to match (e.g. `["GET", "POST"]` or `"*"` for all)
- **Pattern**: Regex pattern to match the route path (e.g. `r"^/users/.*"` or `r".*"` for all)
- **Tags**: A set of OpenAPI tags that must all be present. An empty set (`{}`) means no tag filtering, so the route matches regardless of its tags.
- **MCP type**: What MCP component type to create (`TOOL`, `RESOURCE`, `RESOURCE_TEMPLATE`, or `EXCLUDE`)
- **MCP tags** A set of custom tags to add to components created from matching routes

Here is FastMCP's default rule:

```python
from fastmcp.server.openapi import RouteMap, MCPType

DEFAULT_ROUTE_MAPPINGS = [
    # All routes become tools
    RouteMap(mcp_type=MCPType.TOOL),
]
```

### Custom Route Maps

When creating your FastMCP server, you can customize routing behavior by providing your own list of `RouteMap` objects. Your custom maps are processed before the default route maps, and routes will be assigned to the first matching custom map.

For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `Resource` and `ResourceTemplate` components based on whether they had path parameters. (This was changed solely for client compatibility reasons.) You can restore this behavior by providing custom route maps:

```python {2, 5-10}
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType

# Restore pre-2.8.0 semantic mapping
semantic_maps = [
    # GET requests with path parameters become ResourceTemplates
    RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE),
    # All other GET requests become Resources
    RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
]

mcp = FastMCP.from_openapi(
    ...,
    route_maps=semantic_maps,
)
```

With these maps, `GET` requests are handled semantically, and all other methods (`POST`, `PUT`, etc.) will fall through to the default rule and become `Tool`s.

Here is a more complete example that uses custom route maps to convert all `GET` endpoints under `/analytics/` to tools while excluding all admin endpoints and all routes tagged "internal". All other routes will be handled by the default rules:

```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType

mcp = FastMCP.from_openapi(
    ...,
    route_maps=[

        # Analytics `GET` endpoints are tools
        RouteMap(
            methods=["GET"], 
            pattern=r"^/analytics/.*", 
            mcp_type=MCPType.TOOL,
        ),

        # Exclude all admin endpoints
        RouteMap(
            pattern=r"^/admin/.*", 
            mcp_type=MCPType.EXCLUDE,
        ),

        # Exclude all routes tagged "internal"
        RouteMap(
            tags={"internal"},
            mcp_type=MCPType.EXCLUDE,
        ),
    ],
)
```

<Tip>
The default route maps are always applied after your custom maps, so you do not have to create route maps for every possible route.
</Tip>

### Excluding Routes

To exclude routes from the MCP server, use a route map to assign them to `MCPType.EXCLUDE`. 

You can use this to remove sensitive or internal routes by targeting them specifically:

```python {7,8}
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType

mcp = FastMCP.from_openapi(
    ...,
    route_maps=[
        RouteMap(pattern=r"^/admin/.*", mcp_type=MCPType.EXCLUDE),
        RouteMap(tags={"internal"}, mcp_type=MCPType.EXCLUDE),
    ],
)
```

Or you can use a catch-all rule to exclude everything that your maps don't handle explicitly:
```python {10}
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType

mcp = FastMCP.from_openapi(
    ...,
    route_maps=[
        # custom mapping logic goes here
        ...,
        # exclude all remaining routes
        RouteMap(mcp_type=MCPType.EXCLUDE),
    ],
)
```

<Tip>
Using a catch-all exclusion rule will prevent the default route mappings from being applied, since it will match every remaining route. This is useful if you want to explicitly allow-list certain routes.
</Tip>


### Advanced Route Mapping

<VersionBadge version="2.5.0" />

For advanced use cases that require more complex logic, you can provide a `route_map_fn` callable. After the route map logic is applied, this function is called on each matched route and its assigned MCP component type. It can optionally return a different component type to override the mapped assignment. If it returns `None`, the assigned type is used.

In addition to more precise targeting of methods, patterns, and tags, this function can access any additional OpenAPI metadata about the route.

<Tip>
The `route_map_fn` **is** called on routes that matched `MCPType.EXCLUDE` in your custom maps, giving you an opportunity to override the exclusion.
</Tip>


```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute

def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
    """Advanced route type mapping."""
    # Convert all admin routes to tools regardless of HTTP method
    if "/admin/" in route.path:
        return MCPType.TOOL

    elif "internal" in route.tags:
        return MCPType.EXCLUDE
    
    # Convert user detail routes to templates even if they're POST
    elif route.path.startswith("/users/") and route.method == "POST":
        return MCPType.RESOURCE_TEMPLATE
    
    # Use defaults for all other routes
    return None

mcp = FastMCP.from_openapi(
    ...,
    route_map_fn=custom_route_mapper,
)
```

## Customizing MCP Components

### Tags

<VersionBadge version="2.8.0" />

FastMCP provides several ways to add tags to your MCP components, allowing you to categorize and organize them for better discoverability and filtering. Tags are combined from multiple sources to create the final set of tags on each component.

#### RouteMap Tags

You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map.

```python {12, 20, 28}
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType

mcp = FastMCP.from_openapi(
    ...,
    route_maps=[
        # Add custom tags to all POST endpoints
        RouteMap(
            methods=["POST"],
            pattern=r".*",
            mcp_type=MCPType.TOOL,
            mcp_tags={"write-operation", "api-mutation"}
        ),
        
        # Add different tags to detail view endpoints
        RouteMap(
            methods=["GET"],
            pattern=r".*\{.*\}.*",
            mcp_type=MCPType.RESOURCE_TEMPLATE,
            mcp_tags={"detail-view", "parameterized"}
        ),
        
        # Add tags to list endpoints
        RouteMap(
            methods=["GET"],
            pattern=r".*",
            mcp_type=MCPType.RESOURCE,
            mcp_tags={"list-data", "collection"}
        ),
    ],
)
```

#### Global Tags

You can add tags to **all** components by providing a `tags` parameter when creating your FastMCP server with `from_openapi` or `from_fastapi`. These global tags will be applied to every component created from your OpenAPI specification.

<CodeGroup>
```python {6} from_openapi()
from fastmcp import FastMCP

mcp = FastMCP.from_openapi(
    openapi_spec=spec,
    client=client,
    tags={"api-v2", "production", "external"}
)
```
```python {5} from_fastapi()
from fastmcp import FastMCP

mcp = FastMCP.from_fastapi(
    app=app,
    tags={"internal-api", "microservice"}
)
```
</CodeGroup>


### Names

<VersionBadge version="2.5.0" />

FastMCP automatically generates names for MCP components based on the OpenAPI specification. By default, it uses the `operationId` from your OpenAPI spec, up to the first double underscore (`__`).

All component names are automatically:
- **Slugified**: Spaces and special characters are converted to underscores or removed
- **Truncated**: Limited to 56 characters maximum to ensure compatibility
- **Unique**: If multiple components have the same name, a number is automatically appended to make them unique

For more control over component names, you can provide an `mcp_names` dictionary that maps `operationId` values to your desired names. The `operationId` must be exactly as it appears in the OpenAPI spec. The provided name will always be slugified and truncated.

```python {5-9}
from fastmcp import FastMCP

mcp = FastMCP.from_openapi(
    ...
    mcp_names={
        "list_users__with_pagination": "user_list",
        "create_user__admin_required": "create_user", 
        "get_user_details__admin_required": "user_detail",
    }
)
```

Any `operationId` not found in `mcp_names` will use the default strategy (operationId up to the first `__`).


### Advanced Customization
<VersionBadge version="2.5.0" />

By default, FastMCP creates MCP components using a variety of metadata from the OpenAPI spec, such as incorporating the OpenAPI description into the MCP component description.

At times you may want to modify those MCP components in a variety of ways, such as adding LLM-specific instructions or tags. For fine-grained customization, you can provide a `mcp_component_fn` when creating the MCP server. After each MCP component has been created, this function is called on it and has the opportunity to modify it in-place.

<Tip>
Your `mcp_component_fn` is expected to modify the component in-place, not to return a new component. The result of the function is ignored.
</Tip>

```python {27}
from fastmcp import FastMCP
from fastmcp.server.openapi import (
    HTTPRoute, 
    OpenAPITool, 
    OpenAPIResource, 
    OpenAPIResourceTemplate,
)

def customize_components(
    route: HTTPRoute, 
    component: OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate,
) -> None:
    
    # Add custom tags to all components
    component.tags.add("openapi")
    
    # Customize based on component type
    if isinstance(component, OpenAPITool):
        component.description = f"🔧 {component.description} (via API)"
    
    if isinstance(component, OpenAPIResource):
        component.description = f"📊 {component.description}"
        component.tags.add("data")

mcp = FastMCP.from_openapi(
    ...,
    mcp_component_fn=customize_components,
)
```
## Request Parameter Handling

FastMCP intelligently handles different types of parameters in OpenAPI requests:

### Query Parameters

By default, FastMCP only includes query parameters that have non-empty values. Parameters with `None` values or empty strings are automatically filtered out.

```python
# When calling this tool...
await client.call_tool("search_products", {
    "category": "electronics",  # ✅ Included
    "min_price": 100,           # ✅ Included  
    "max_price": None,          # ❌ Excluded
    "brand": "",                # ❌ Excluded
})

# The HTTP request will be: GET /products?category=electronics&min_price=100
```

### Path Parameters

Path parameters are typically required by REST APIs. FastMCP:
- Filters out `None` values
- Validates that all required path parameters are provided
- Raises clear errors for missing required parameters

```python
# ✅ This works
await client.call_tool("get_user", {"user_id": 123})

# ❌ This raises: "Missing required path parameters: {'user_id'}"
await client.call_tool("get_user", {"user_id": None})
```

### Array Parameters

FastMCP handles array parameters according to OpenAPI specifications:

- **Query arrays**: Serialized based on the `explode` parameter (default: `True`)
- **Path arrays**: Serialized as comma-separated values (OpenAPI 'simple' style)

```python
# Query array with explode=true (default)
# ?tags=red&tags=blue&tags=green

# Query array with explode=false  
# ?tags=red,blue,green

# Path array (always comma-separated)
# /items/red,blue,green
```

### Headers

Header parameters are automatically converted to strings and included in the HTTP request.

## Auth

If your API requires authentication, configure it on the HTTP client before creating the MCP server:

```python
import httpx
from fastmcp import FastMCP

# Bearer token authentication
api_client = httpx.AsyncClient(
    base_url="https://api.example.com",
    headers={"Authorization": "Bearer YOUR_TOKEN"}
)

# Create MCP server with authenticated client
mcp = FastMCP.from_openapi(..., client=api_client)
```
## Timeouts

Set a timeout for all API requests:

```python
mcp = FastMCP.from_openapi(
    openapi_spec=spec, 
    client=api_client,
    timeout=30.0  # 30 second timeout for all requests
)
```


## FastAPI Integration

<VersionBadge version="2.0.0" />

FastMCP can directly convert FastAPI applications into MCP servers by extracting their OpenAPI specifications:

<Tip>
FastMCP does *not* include FastAPI as a dependency; you must install it separately to use this integration.
</Tip>

```python
from fastapi import FastAPI
from fastmcp import FastMCP

# Your FastAPI app
app = FastAPI(title="My API", version="1.0.0")

@app.get("/items", tags=["items"], operation_id="list_items")
def list_items():
    return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]

@app.get("/items/{item_id}", tags=["items", "detail"], operation_id="get_item")
def get_item(item_id: int):
    return {"id": item_id, "name": f"Item {item_id}"}

@app.post("/items", tags=["items", "create"], operation_id="create_item")
def create_item(name: str):
    return {"id": 3, "name": name}

# Convert FastAPI app to MCP server
mcp = FastMCP.from_fastapi(app=app)

if __name__ == "__main__":
    mcp.run()  # Run as MCP server
```

Note that operation ids are optional, but are used to create component names. You can also provide custom names, just like with OpenAPI specs.

<Warning>
FastMCP servers are not FastAPI apps, even when created from one. To learn how to deploy them as an ASGI app, see the [ASGI Integration](/deployment/asgi) documentation.
</Warning>



### FastAPI Configuration

All OpenAPI integration features work with FastAPI apps:

```python
from fastmcp.server.openapi import RouteMap, MCPType

# Custom route mapping with FastAPI
mcp = FastMCP.from_fastapi(
    app=app,
    name="My Custom Server",
    timeout=5.0,
    tags={"api-v1", "fastapi"},  # Global tags for all components
    mcp_names={"operationId": "friendly_name"},  # Custom component names
    route_maps=[
        # Admin endpoints become tools with custom tags
        RouteMap(
            methods="*", 
            pattern=r"^/admin/.*", 
            mcp_type=MCPType.TOOL,
            mcp_tags={"admin", "privileged"}
        ),
        # Internal endpoints are excluded
        RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.EXCLUDE, tags={"internal"}),
    ],
    route_map_fn=my_route_mapper,
    mcp_component_fn=my_component_customizer,
    mcp_names={
        "get_user_details_users__user_id__get": "get_user_details",
    }
)
```

### FastAPI Benefits

- **Zero code duplication**: Reuse existing FastAPI endpoints
- **Schema inheritance**: Pydantic models and validation are preserved  
- **ASGI transport**: Direct in-memory communication (no HTTP overhead)
- **Full FastAPI features**: Dependencies, middleware, authentication all work

================
File: docs/servers/progress.mdx
================
---
title: Progress Reporting
sidebarTitle: Progress
description: Update clients on the progress of long-running operations through the MCP context.
icon: chart-line
---

import { VersionBadge } from '/snippets/version-badge.mdx'

Progress reporting allows MCP tools to notify clients about the progress of long-running operations. This enables clients to display progress indicators and provide better user experience during time-consuming tasks.

## Why Use Progress Reporting?

Progress reporting is valuable for:

- **User experience**: Keep users informed about long-running operations
- **Progress indicators**: Enable clients to show progress bars or percentages
- **Timeout prevention**: Demonstrate that operations are actively progressing
- **Debugging**: Track execution progress for performance analysis

### Basic Usage

Use `ctx.report_progress()` to send progress updates to the client:

```python {14, 21}
from fastmcp import FastMCP, Context
import asyncio

mcp = FastMCP("ProgressDemo")

@mcp.tool
async def process_items(items: list[str], ctx: Context) -> dict:
    """Process a list of items with progress updates."""
    total = len(items)
    results = []
    
    for i, item in enumerate(items):
        # Report progress as we process each item
        await ctx.report_progress(progress=i, total=total)
        
        # Simulate processing time
        await asyncio.sleep(0.1)
        results.append(item.upper())
    
    # Report 100% completion
    await ctx.report_progress(progress=total, total=total)
    
    return {"processed": len(results), "results": results}
```

## Method Signature

<Card icon="code" title="Context Progress Method">
<ResponseField name="ctx.report_progress" type="async method">
  Report progress to the client for long-running operations
  
  <Expandable title="Parameters">
    <ResponseField name="progress" type="float">
      Current progress value (e.g., 24, 0.75, 1500)
    </ResponseField>
    
    <ResponseField name="total" type="float | None" default="None">
      Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation.
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

## Progress Patterns

### Percentage-Based Progress

Report progress as a percentage (0-100):

```python {13-14}
@mcp.tool
async def download_file(url: str, ctx: Context) -> str:
    """Download a file with percentage progress."""
    total_size = 1000  # KB
    downloaded = 0
    
    while downloaded < total_size:
        # Download chunk
        chunk_size = min(50, total_size - downloaded)
        downloaded += chunk_size
        
        # Report percentage progress
        percentage = (downloaded / total_size) * 100
        await ctx.report_progress(progress=percentage, total=100)
        
        await asyncio.sleep(0.1)  # Simulate download time
    
    return f"Downloaded file from {url}"
```

### Absolute Progress

Report progress with absolute values:

```python {10}
@mcp.tool
async def backup_database(ctx: Context) -> str:
    """Backup database tables with absolute progress."""
    tables = ["users", "orders", "products", "inventory", "logs"]
    
    for i, table in enumerate(tables):
        await ctx.info(f"Backing up table: {table}")
        
        # Report absolute progress
        await ctx.report_progress(progress=i + 1, total=len(tables))
        
        # Simulate backup time
        await asyncio.sleep(0.5)
    
    return "Database backup completed"
```

### Indeterminate Progress

Report progress without a known total for operations where the endpoint is unknown:

```python {11}
@mcp.tool
async def scan_directory(directory: str, ctx: Context) -> dict:
    """Scan directory with indeterminate progress."""
    files_found = 0
    
    # Simulate directory scanning
    for i in range(10):  # Unknown number of files
        files_found += 1
        
        # Report progress without total for indeterminate operations
        await ctx.report_progress(progress=files_found)
        
        await asyncio.sleep(0.2)
    
    return {"files_found": files_found, "directory": directory}
```

### Multi-Stage Operations

Break complex operations into stages with progress for each:

```python
@mcp.tool
async def data_migration(source: str, destination: str, ctx: Context) -> str:
    """Migrate data with multi-stage progress reporting."""
    
    # Stage 1: Validation (0-25%)
    await ctx.info("Validating source data")
    for i in range(5):
        await ctx.report_progress(progress=i * 5, total=100)
        await asyncio.sleep(0.1)
    
    # Stage 2: Export (25-60%)
    await ctx.info("Exporting data from source")
    for i in range(7):
        progress = 25 + (i * 5)
        await ctx.report_progress(progress=progress, total=100)
        await asyncio.sleep(0.1)
    
    # Stage 3: Transform (60-80%)
    await ctx.info("Transforming data format")
    for i in range(4):
        progress = 60 + (i * 5)
        await ctx.report_progress(progress=progress, total=100)
        await asyncio.sleep(0.1)
    
    # Stage 4: Import (80-100%)
    await ctx.info("Importing to destination")
    for i in range(4):
        progress = 80 + (i * 5)
        await ctx.report_progress(progress=progress, total=100)
        await asyncio.sleep(0.1)
    
    # Final completion
    await ctx.report_progress(progress=100, total=100)
    
    return f"Migration from {source} to {destination} completed"
```


## Client Requirements

Progress reporting requires clients to support progress handling:

- Clients must send a `progressToken` in the initial request to receive progress updates
- If no progress token is provided, progress calls will have no effect (they won't error)
- See [Client Progress](/clients/progress) for details on implementing client-side progress handling

================
File: docs/servers/prompts.mdx
================
---
title: Prompts
sidebarTitle: Prompts
description: Create reusable, parameterized prompt templates for MCP clients.
icon: message-lines
---

import { VersionBadge } from "/snippets/version-badge.mdx"

Prompts are reusable message templates that help LLMs generate structured, purposeful responses. FastMCP simplifies defining these templates, primarily using the `@mcp.prompt` decorator.

## What Are Prompts?

Prompts provide parameterized message templates for LLMs. When a client requests a prompt:

1.  FastMCP finds the corresponding prompt definition.
2.  If it has parameters, they are validated against your function signature.
3.  Your function executes with the validated inputs.
4.  The generated message(s) are returned to the LLM to guide its response.

This allows you to define consistent, reusable templates that LLMs can use across different clients and contexts.

## Prompts

### The `@prompt` Decorator

The most common way to define a prompt is by decorating a Python function. The decorator uses the function name as the prompt's identifier.

```python
from fastmcp import FastMCP
from fastmcp.prompts.prompt import Message, PromptMessage, TextContent

mcp = FastMCP(name="PromptServer")

# Basic prompt returning a string (converted to user message automatically)
@mcp.prompt
def ask_about_topic(topic: str) -> str:
    """Generates a user message asking for an explanation of a topic."""
    return f"Can you please explain the concept of '{topic}'?"

# Prompt returning a specific message type
@mcp.prompt
def generate_code_request(language: str, task_description: str) -> PromptMessage:
    """Generates a user message requesting code generation."""
    content = f"Write a {language} function that performs the following task: {task_description}"
    return PromptMessage(role="user", content=TextContent(type="text", text=content))
```

**Key Concepts:**

*   **Name:** By default, the prompt name is taken from the function name.
*   **Parameters:** The function parameters define the inputs needed to generate the prompt.
*   **Inferred Metadata:** By default:
    *   Prompt Name: Taken from the function name (`ask_about_topic`).
    *   Prompt Description: Taken from the function's docstring.
<Tip>
Functions with `*args` or `**kwargs` are not supported as prompts. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists.
</Tip>

#### Decorator Arguments

While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.prompt` decorator:

```python
@mcp.prompt(
    name="analyze_data_request",          # Custom prompt name
    description="Creates a request to analyze data with specific parameters",  # Custom description
    tags={"analysis", "data"}             # Optional categorization tags
)
def data_analysis_prompt(
    data_uri: str = Field(description="The URI of the resource containing the data."),
    analysis_type: str = Field(default="summary", description="Type of analysis.")
) -> str:
    """This docstring is ignored when description is provided."""
    return f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
```

<Card icon="code" title="@prompt Decorator Arguments">
<ParamField body="name" type="str | None">
  Sets the explicit prompt name exposed via MCP. If not provided, uses the function name
</ParamField>

<ParamField body="description" type="str | None">
  Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose
</ParamField>

<ParamField body="tags" type="set[str] | None">
  A set of strings used to categorize the prompt. Clients might use tags to filter or group available prompts
</ParamField>

<ParamField body="enabled" type="bool" default="True">
  A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
</ParamField>
</Card>

### Argument Types

<VersionBadge version="2.9.0" />

The MCP specification requires that all prompt arguments be passed as strings, but FastMCP allows you to use typed annotations for better developer experience. When you use complex types like `list[int]` or `dict[str, str]`, FastMCP:

1. **Automatically converts** string arguments from MCP clients to the expected types
2. **Generates helpful descriptions** showing the exact JSON string format needed
3. **Preserves direct usage** - you can still call prompts with properly typed arguments

Since the MCP specification only allows string arguments, clients need to know what string format to use for complex types. FastMCP solves this by automatically enhancing the argument descriptions with JSON schema information, making it clear to both humans and LLMs how to format their arguments.

<CodeGroup>

```python Python Code
@mcp.prompt
def analyze_data(
    numbers: list[int],
    metadata: dict[str, str], 
    threshold: float
) -> str:
    """Analyze numerical data."""
    avg = sum(numbers) / len(numbers)
    return f"Average: {avg}, above threshold: {avg > threshold}"
```

```json Resulting MCP Prompt
{
  "name": "analyze_data",
  "description": "Analyze numerical data.",
  "arguments": [
    {
      "name": "numbers",
      "description": "Provide as a JSON string matching the following schema: {\"items\":{\"type\":\"integer\"},\"type\":\"array\"}",
      "required": true
    },
    {
      "name": "metadata", 
      "description": "Provide as a JSON string matching the following schema: {\"additionalProperties\":{\"type\":\"string\"},\"type\":\"object\"}",
      "required": true
    },
    {
      "name": "threshold",
      "description": "Provide as a JSON string matching the following schema: {\"type\":\"number\"}",
      "required": true
    }
  ]
}
```

</CodeGroup>

**MCP clients will call this prompt with string arguments:**
```json
{
  "numbers": "[1, 2, 3, 4, 5]",
  "metadata": "{\"source\": \"api\", \"version\": \"1.0\"}",
  "threshold": "2.5"
}
```

**But you can still call it directly with proper types:**
```python
# This also works for direct calls
result = await prompt.render({
    "numbers": [1, 2, 3, 4, 5],
    "metadata": {"source": "api", "version": "1.0"}, 
    "threshold": 2.5
})
```

<Warning>
Keep your type annotations simple when using this feature. Complex nested types or custom classes may not convert reliably from JSON strings. The automatically generated schema descriptions are the only guidance users receive about the expected format.

Good choices: `list[int]`, `dict[str, str]`, `float`, `bool`
Avoid: Complex Pydantic models, deeply nested structures, custom classes
</Warning>

### Return Values

FastMCP intelligently handles different return types from your prompt function:

-   **`str`**: Automatically converted to a single `PromptMessage`.
-   **`PromptMessage`**: Used directly as provided. (Note a more user-friendly `Message` constructor is available that can accept raw strings instead of `TextContent` objects.)
-   **`list[PromptMessage | str]`**: Used as a sequence of messages (a conversation).
-   **`Any`**: If the return type is not one of the above, the return value is attempted to be converted to a string and used as a `PromptMessage`.

```python
from fastmcp.prompts.prompt import Message

@mcp.prompt
def roleplay_scenario(character: str, situation: str) -> list[Message]:
    """Sets up a roleplaying scenario with initial messages."""
    return [
        Message(f"Let's roleplay. You are {character}. The situation is: {situation}"),
        Message("Okay, I understand. I am ready. What happens next?", role="assistant")
    ]
```


### Required vs. Optional Parameters

Parameters in your function signature are considered **required** unless they have a default value.

```python
@mcp.prompt
def data_analysis_prompt(
    data_uri: str,                        # Required - no default value
    analysis_type: str = "summary",       # Optional - has default value
    include_charts: bool = False          # Optional - has default value
) -> str:
    """Creates a request to analyze data with specific parameters."""
    prompt = f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
    if include_charts:
        prompt += " Include relevant charts and visualizations."
    return prompt
```

In this example, the client *must* provide `data_uri`. If `analysis_type` or `include_charts` are omitted, their default values will be used.

### Disabling Prompts

<VersionBadge version="2.8.0" />

You can control the visibility and availability of prompts by enabling or disabling them. Disabled prompts will not appear in the list of available prompts, and attempting to call a disabled prompt will result in an "Unknown prompt" error.

By default, all prompts are enabled. You can disable a prompt upon creation using the `enabled` parameter in the decorator:

```python
@mcp.prompt(enabled=False)
def experimental_prompt():
    """This prompt is not ready for use."""
    return "This is an experimental prompt."
```

You can also toggle a prompt's state programmatically after it has been created:

```python
@mcp.prompt
def seasonal_prompt(): return "Happy Holidays!"

# Disable and re-enable the prompt
seasonal_prompt.disable()
seasonal_prompt.enable()
```

### Async Prompts

FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.

```python
# Synchronous prompt
@mcp.prompt
def simple_question(question: str) -> str:
    """Generates a simple question to ask the LLM."""
    return f"Question: {question}"

# Asynchronous prompt
@mcp.prompt
async def data_based_prompt(data_id: str) -> str:
    """Generates a prompt based on data that needs to be fetched."""
    # In a real scenario, you might fetch data from a database or API
    async with aiohttp.ClientSession() as session:
        async with session.get(f"https://api.example.com/data/{data_id}") as response:
            data = await response.json()
            return f"Analyze this data: {data['content']}"
```

Use `async def` when your prompt function performs I/O operations like network requests, database queries, file I/O, or external service calls.

### Accessing MCP Context

<VersionBadge version="2.2.5" />

Prompts can access additional MCP information and features through the `Context` object. To access it, add a parameter to your prompt function with a type annotation of `Context`:

```python {6}
from fastmcp import FastMCP, Context

mcp = FastMCP(name="PromptServer")

@mcp.prompt
async def generate_report_request(report_type: str, ctx: Context) -> str:
    """Generates a request for a report."""
    return f"Please create a {report_type} report. Request ID: {ctx.request_id}"
```

For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).

### Notifications

<VersionBadge version="2.9.1" />

FastMCP automatically sends `notifications/prompts/list_changed` notifications to connected clients when prompts are added, enabled, or disabled. This allows clients to stay up-to-date with the current prompt set without manually polling for changes.

```python
@mcp.prompt
def example_prompt() -> str:
    return "Hello!"

# These operations trigger notifications:
mcp.add_prompt(example_prompt)  # Sends prompts/list_changed notification
example_prompt.disable()        # Sends prompts/list_changed notification  
example_prompt.enable()         # Sends prompts/list_changed notification
```

Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their prompt lists or update their interfaces.

## Server Behavior

### Duplicate Prompts

<VersionBadge version="2.1.0" />

You can configure how the FastMCP server handles attempts to register multiple prompts with the same name. Use the `on_duplicate_prompts` setting during `FastMCP` initialization.

```python
from fastmcp import FastMCP

mcp = FastMCP(
    name="PromptServer",
    on_duplicate_prompts="error"  # Raise an error if a prompt name is duplicated
)

@mcp.prompt
def greeting(): return "Hello, how can I help you today?"

# This registration attempt will raise a ValueError because
# "greeting" is already registered and the behavior is "error".
# @mcp.prompt
# def greeting(): return "Hi there! What can I do for you?"
```

The duplicate behavior options are:

-   `"warn"` (default): Logs a warning, and the new prompt replaces the old one.
-   `"error"`: Raises a `ValueError`, preventing the duplicate registration.
-   `"replace"`: Silently replaces the existing prompt with the new one.
-   `"ignore"`: Keeps the original prompt and ignores the new registration attempt.

================
File: docs/servers/proxy.mdx
================
---
title: Proxy Servers
sidebarTitle: Proxy Servers
description: Use FastMCP to act as an intermediary or change transport for other MCP servers.
icon: arrows-retweet
---
import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

FastMCP provides a powerful proxying capability that allows one FastMCP server instance to act as a frontend for another MCP server (which could be remote, running on a different transport, or even another FastMCP instance). This is achieved using the `FastMCP.as_proxy()` class method.

`as_proxy()` accepts either an existing `Client` or any argument that can be passed to a `Client` as its `transport` parameter&mdash;such as another `FastMCP` instance, a URL to a remote server, or an MCP configuration dictionary.

## What is Proxying?

Proxying means setting up a FastMCP server that doesn't implement its own tools or resources directly. Instead, when it receives a request (like `tools/call` or `resources/read`), it forwards that request to a *backend* MCP server, receives the response, and then relays that response back to the original client.


```mermaid
sequenceDiagram
    participant ClientApp as Your Client (e.g., Claude Desktop)
    participant FastMCPProxy as FastMCP Proxy Server
    participant BackendServer as Backend MCP Server (e.g., remote SSE)

    ClientApp->>FastMCPProxy: MCP Request (e.g. stdio)
    Note over FastMCPProxy, BackendServer: Proxy forwards the request
    FastMCPProxy->>BackendServer: MCP Request (e.g. sse)
    BackendServer-->>FastMCPProxy: MCP Response (e.g. sse)
    Note over ClientApp, FastMCPProxy: Proxy relays the response
    FastMCPProxy-->>ClientApp: MCP Response (e.g. stdio)
```
### Use Cases

-   **Transport Bridging**: Expose a server running on one transport (e.g., a remote SSE server) via a different transport (e.g., local Stdio for Claude Desktop).
-   **Adding Functionality**: Insert a layer in front of an existing server to add caching, logging, authentication, or modify requests/responses (though direct modification requires subclassing `FastMCPProxy`).
-   **Security Boundary**: Use the proxy as a controlled gateway to an internal server.
-   **Simplifying Client Configuration**: Provide a single, stable endpoint (the proxy) even if the backend server's location or transport changes.

## Creating a Proxy

The easiest way to create a proxy is using the `FastMCP.as_proxy()` class method. This creates a standard FastMCP server that forwards requests to another MCP server.

```python
from fastmcp import FastMCP

# Provide the backend in any form accepted by Client
proxy_server = FastMCP.as_proxy(
    "backend_server.py",  # Could also be a FastMCP instance, config dict, or a remote URL
    name="MyProxyServer"  # Optional settings for the proxy
)

# Or create the Client yourself for custom configuration
backend_client = Client("backend_server.py")
proxy_from_client = FastMCP.as_proxy(backend_client)
```

**How `as_proxy` Works:**

1.  It connects to the backend server using the provided client.
2.  It discovers all the tools, resources, resource templates, and prompts available on the backend server.
3.  It creates corresponding "proxy" components that forward requests to the backend.
4.  It returns a standard `FastMCP` server instance that can be used like any other.

<Note>
Currently, proxying focuses primarily on exposing the major MCP objects (tools, resources, templates, and prompts). Some advanced MCP features like notifications and sampling are not fully supported in proxies in the current version. Support for these additional features may be added in future releases.
</Note>

### Bridging Transports

A common use case is to bridge transports. For example, making a remote SSE server available locally via Stdio:

```python
from fastmcp import FastMCP

# Target a remote SSE server directly by URL
proxy = FastMCP.as_proxy("http://example.com/mcp/sse", name="SSE to Stdio Proxy")

# The proxy can now be used with any transport
# No special handling needed - it works like any FastMCP server
```

### In-Memory Proxies

You can also proxy an in-memory `FastMCP` instance, which is useful for adjusting the configuration or behavior of a server you don't completely control.

```python
from fastmcp import FastMCP

# Original server
original_server = FastMCP(name="Original")

@original_server.tool
def tool_a() -> str: 
    return "A"

# Create a proxy of the original server directly
proxy = FastMCP.as_proxy(
    original_server,
    name="Proxy Server"
)

# proxy is now a regular FastMCP server that forwards
# requests to original_server
```

### Configuration-Based Proxies

<VersionBadge version="2.4.0" />

You can create a proxy directly from a configuration dictionary that follows the MCPConfig schema. This is useful for quickly setting up proxies to remote servers without manually configuring each connection detail.

```python
from fastmcp import FastMCP

# Create a proxy directly from a config dictionary
config = {
    "mcpServers": {
        "default": {  # For single server configs, 'default' is commonly used
            "url": "https://example.com/mcp",
            "transport": "http"
        }
    }
}

# Create a proxy to the configured server
proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")

# Run the proxy with stdio transport for local access
if __name__ == "__main__":
    proxy.run()
```

<Note>
The MCPConfig format follows an emerging standard for MCP server configuration and may evolve as the specification matures. While FastMCP aims to maintain compatibility with future versions, be aware that field names or structure might change.
</Note>

You can also use MCPConfig to create a proxy to multiple servers. When multiple servers are specified, they are automatically mounted with their config names as prefixes, providing a unified interface to all servers:

```python
from fastmcp import FastMCP

# Multi-server configuration
config = {
    "mcpServers": {
        "weather": {
            "url": "https://weather-api.example.com/mcp",
            "transport": "http"
        },
        "calendar": {
            "url": "https://calendar-api.example.com/mcp",
            "transport": "http"
        }
    }
}

# Create a proxy to multiple servers
composite_proxy = FastMCP.as_proxy(config, name="Composite Proxy")

# Tools and resources are accessible with prefixes:
# - weather_get_forecast, calendar_add_event 
# - weather://weather/icons/sunny, calendar://calendar/events/today
```

## `FastMCPProxy` Class

Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally don't need to interact with this class directly, but it's available if needed.

Using the class directly might be necessary for advanced scenarios, like subclassing `FastMCPProxy` to add custom logic before or after forwarding requests.

================
File: docs/servers/resources.mdx
================
---
title: Resources & Templates
sidebarTitle: Resources
description: Expose data sources and dynamic content generators to your MCP client.
icon: folder-open
---

import { VersionBadge } from "/snippets/version-badge.mdx"

Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI.

FastMCP simplifies defining both static and dynamic resources, primarily using the `@mcp.resource` decorator.

## What Are Resources?

Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:

1.  FastMCP finds the corresponding resource definition.
2.  If it's dynamic (defined by a function), the function is executed.
3.  The content (text, JSON, binary data) is returned to the client.

This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.

## Resources

### The `@resource` Decorator

The most common way to define a resource is by decorating a Python function. The decorator requires the resource's unique URI.

```python
import json
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Basic dynamic resource returning a string
@mcp.resource("resource://greeting")
def get_greeting() -> str:
    """Provides a simple greeting message."""
    return "Hello from FastMCP Resources!"

# Resource returning JSON data (dict is auto-serialized)
@mcp.resource("data://config")
def get_config() -> dict:
    """Provides application configuration as JSON."""
    return {
        "theme": "dark",
        "version": "1.2.0",
        "features": ["tools", "resources"],
    }
```

**Key Concepts:**

*   **URI:** The first argument to `@resource` is the unique URI (e.g., `"resource://greeting"`) clients use to request this data.
*   **Lazy Loading:** The decorated function (`get_greeting`, `get_config`) is only executed when a client specifically requests that resource URI via `resources/read`.
*   **Inferred Metadata:** By default:
    *   Resource Name: Taken from the function name (`get_greeting`).
    *   Resource Description: Taken from the function's docstring.

#### Decorator Arguments

You can customize the resource's properties using arguments in the `@mcp.resource` decorator:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Example specifying metadata
@mcp.resource(
    uri="data://app-status",      # Explicit URI (required)
    name="ApplicationStatus",     # Custom name
    description="Provides the current status of the application.", # Custom description
    mime_type="application/json", # Explicit MIME type
    tags={"monitoring", "status"} # Categorization tags
)
def get_application_status() -> dict:
    """Internal function description (ignored if description is provided above)."""
    return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage
```

<Card icon="code" title="@resource Decorator Arguments">
<ParamField body="uri" type="str" required>
  The unique identifier for the resource
</ParamField>

<ParamField body="name" type="str | None">
  A human-readable name. If not provided, defaults to function name
</ParamField>

<ParamField body="description" type="str | None">
  Explanation of the resource. If not provided, defaults to docstring
</ParamField>

<ParamField body="mime_type" type="str | None">
  Specifies the content type. FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types
</ParamField>

<ParamField body="tags" type="set[str] | None">
  A set of strings for categorization, potentially used by clients for filtering
</ParamField>

<ParamField body="enabled" type="bool" default="True">
  A boolean to enable or disable the resource. See [Disabling Resources](#disabling-resources) for more information
</ParamField>
</Card>

### Return Values

FastMCP automatically converts your function's return value into the appropriate MCP resource content:

-   **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default).
-   **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default).
-   **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`).
-   **`None`**: Results in an empty resource content list being returned.

### Disabling Resources

<VersionBadge version="2.8.0" />

You can control the visibility and availability of resources and templates by enabling or disabling them. Disabled resources will not appear in the list of available resources or templates, and attempting to read a disabled resource will result in an "Unknown resource" error.

By default, all resources are enabled. You can disable a resource upon creation using the `enabled` parameter in the decorator:

```python
@mcp.resource("data://secret", enabled=False)
def get_secret_data():
    """This resource is currently disabled."""
    return "Secret data"
```

You can also toggle a resource's state programmatically after it has been created:

```python
@mcp.resource("data://config")
def get_config(): return {"version": 1}

# Disable and re-enable the resource
get_config.disable()
get_config.enable()
```


### Accessing MCP Context

<VersionBadge version="2.2.5" />

Resources and resource templates can access additional MCP information and features through the `Context` object. To access it, add a parameter to your resource function with a type annotation of `Context`:

```python {6, 14}
from fastmcp import FastMCP, Context

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://system-status")
async def get_system_status(ctx: Context) -> dict:
    """Provides system status information."""
    return {
        "status": "operational",
        "request_id": ctx.request_id
    }

@mcp.resource("resource://{name}/details")
async def get_details(name: str, ctx: Context) -> dict:
    """Get details for a specific name."""
    return {
        "name": name,
        "accessed_at": ctx.request_id
    }
```

For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).


### Async Resources

Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.

```python
import aiofiles
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

@mcp.resource("file:///app/data/important_log.txt", mime_type="text/plain")
async def read_important_log() -> str:
    """Reads content from a specific log file asynchronously."""
    try:
        async with aiofiles.open("/app/data/important_log.txt", mode="r") as f:
            content = await f.read()
        return content
    except FileNotFoundError:
        return "Log file not found."
```


### Resource Classes

While `@mcp.resource` is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using `mcp.add_resource()` and concrete `Resource` subclasses.

```python
from pathlib import Path
from fastmcp import FastMCP
from fastmcp.resources import FileResource, TextResource, DirectoryResource

mcp = FastMCP(name="DataServer")

# 1. Exposing a static file directly
readme_path = Path("./README.md").resolve()
if readme_path.exists():
    # Use a file:// URI scheme
    readme_resource = FileResource(
        uri=f"file://{readme_path.as_posix()}",
        path=readme_path, # Path to the actual file
        name="README File",
        description="The project's README.",
        mime_type="text/markdown",
        tags={"documentation"}
    )
    mcp.add_resource(readme_resource)

# 2. Exposing simple, predefined text
notice_resource = TextResource(
    uri="resource://notice",
    name="Important Notice",
    text="System maintenance scheduled for Sunday.",
    tags={"notification"}
)
mcp.add_resource(notice_resource)

# 3. Using a custom key different from the URI
special_resource = TextResource(
    uri="resource://common-notice",
    name="Special Notice",
    text="This is a special notice with a custom storage key.",
)
mcp.add_resource(special_resource, key="resource://custom-key")

# 4. Exposing a directory listing
data_dir_path = Path("./app_data").resolve()
if data_dir_path.is_dir():
    data_listing_resource = DirectoryResource(
        uri="resource://data-files",
        path=data_dir_path, # Path to the directory
        name="Data Directory Listing",
        description="Lists files available in the data directory.",
        recursive=False # Set to True to list subdirectories
    )
    mcp.add_resource(data_listing_resource) # Returns JSON list of files
```

**Common Resource Classes:**

-   `TextResource`: For simple string content.
-   `BinaryResource`: For raw `bytes` content.
-   `FileResource`: Reads content from a local file path. Handles text/binary modes and lazy reading.
-   `HttpResource`: Fetches content from an HTTP(S) URL (requires `httpx`).
-   `DirectoryResource`: Lists files in a local directory (returns JSON).
-   (`FunctionResource`: Internal class used by `@mcp.resource`).

Use these when the content is static or sourced directly from a file/URL, bypassing the need for a dedicated Python function.

#### Custom Resource Keys

<VersionBadge version="2.2.0" />

When adding resources directly with `mcp.add_resource()`, you can optionally provide a custom storage key:

```python
# Creating a resource with standard URI as the key
resource = TextResource(uri="resource://data")
mcp.add_resource(resource)  # Will be stored and accessed using "resource://data"

# Creating a resource with a custom key
special_resource = TextResource(uri="resource://special-data")
mcp.add_resource(special_resource, key="internal://data-v2")  # Will be stored and accessed using "internal://data-v2"
```

Note that this parameter is only available when using `add_resource()` directly and not through the `@resource` decorator, as URIs are provided explicitly when using the decorator.

### Notifications

<VersionBadge version="2.9.1" />

FastMCP automatically sends `notifications/resources/list_changed` notifications to connected clients when resources or templates are added, enabled, or disabled. This allows clients to stay up-to-date with the current resource set without manually polling for changes.

```python
@mcp.resource("data://example")
def example_resource() -> str:
    return "Hello!"

# These operations trigger notifications:
mcp.add_resource(example_resource)  # Sends resources/list_changed notification
example_resource.disable()          # Sends resources/list_changed notification  
example_resource.enable()           # Sends resources/list_changed notification
```

Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their resource lists or update their interfaces.

## Resource Templates

Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.

Resource templates share most configuration options with regular resources (name, description, mime_type, tags), but add the ability to define URI parameters that map to function parameters.

Resource templates generate a new resource for each unique set of parameters, which means that resources can be dynamically created on-demand. For example, if the resource template `"user://profile/{name}"` is registered, MCP clients could request `"user://profile/ford"` or `"user://profile/marvin"` to retrieve either of those two user profiles as resources, without having to register each resource individually.

<Tip>
Functions with `*args` are not supported as resource templates. However, unlike tools and prompts, resource templates do support `**kwargs` because the URI template defines specific parameter names that will be collected and passed as keyword arguments.
</Tip>

Here is a complete example that shows how to define two resource templates:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Template URI includes {city} placeholder
@mcp.resource("weather://{city}/current")
def get_weather(city: str) -> dict:
    """Provides weather information for a specific city."""
    # In a real implementation, this would call a weather API
    # Here we're using simplified logic for example purposes
    return {
        "city": city.capitalize(),
        "temperature": 22,
        "condition": "Sunny",
        "unit": "celsius"
    }

# Template with multiple parameters
@mcp.resource("repos://{owner}/{repo}/info")
def get_repo_info(owner: str, repo: str) -> dict:
    """Retrieves information about a GitHub repository."""
    # In a real implementation, this would call the GitHub API
    return {
        "owner": owner,
        "name": repo,
        "full_name": f"{owner}/{repo}",
        "stars": 120,
        "forks": 48
    }
```

With these two templates defined, clients can request a variety of resources:
- `weather://london/current` → Returns weather for London
- `weather://paris/current` → Returns weather for Paris
- `repos://jlowin/fastmcp/info` → Returns info about the jlowin/fastmcp repository
- `repos://prefecthq/prefect/info` → Returns info about the prefecthq/prefect repository

### Wildcard Parameters

<VersionBadge version="2.2.4" />

<Tip>
Please note: FastMCP's support for wildcard parameters is an **extension** of the Model Context Protocol standard, which otherwise follows RFC 6570. Since all template processing happens in the FastMCP server, this should not cause any compatibility issues with other MCP implementations.
</Tip>


Resource templates support wildcard parameters that can match multiple path segments. While standard parameters (`{param}`) only match a single path segment and don't cross "/" boundaries, wildcard parameters (`{param*}`) can capture multiple segments including slashes. Wildcards capture all subsequent path segments *up until* the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template.

```python {15, 23}
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")


# Standard parameter only matches one segment
@mcp.resource("files://{filename}")
def get_file(filename: str) -> str:
    """Retrieves a file by name."""
    # Will only match files://<single-segment>
    return f"File content for: {filename}"


# Wildcard parameter can match multiple segments
@mcp.resource("path://{filepath*}")
def get_path_content(filepath: str) -> str:
    """Retrieves content at a specific path."""
    # Can match path://docs/server/resources.mdx
    return f"Content at path: {filepath}"


# Mixing standard and wildcard parameters
@mcp.resource("repo://{owner}/{path*}/template.py")
def get_template_file(owner: str, path: str) -> dict:
    """Retrieves a file from a specific repository and path, but 
    only if the resource ends with `template.py`"""
    # Can match repo://jlowin/fastmcp/src/resources/template.py
    return {
        "owner": owner,
        "path": path + "/template.py",
        "content": f"File at {path}/template.py in {owner}'s repository"
    }
```

Wildcard parameters are useful when:

- Working with file paths or hierarchical data
- Creating APIs that need to capture variable-length path segments
- Building URL-like patterns similar to REST APIs

Note that like regular parameters, each wildcard parameter must still be a named parameter in your function signature, and all required function parameters must appear in the URI template.

### Default Values

<VersionBadge version="2.2.0" />

When creating resource templates, FastMCP enforces two rules for the relationship between URI template parameters and function parameters:

1. **Required Function Parameters:** All function parameters without default values (required parameters) must appear in the URI template.
2. **URI Parameters:** All URI template parameters must exist as function parameters.

However, function parameters with default values don't need to be included in the URI template. When a client requests a resource, FastMCP will:

- Extract parameter values from the URI for parameters included in the template
- Use default values for any function parameters not in the URI template

This allows for flexible API designs. For example, a simple search template with optional parameters:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

@mcp.resource("search://{query}")
def search_resources(query: str, max_results: int = 10, include_archived: bool = False) -> dict:
    """Search for resources matching the query string."""
    # Only 'query' is required in the URI, the other parameters use their defaults
    results = perform_search(query, limit=max_results, archived=include_archived)
    return {
        "query": query,
        "max_results": max_results,
        "include_archived": include_archived,
        "results": results
    }
```

With this template, clients can request `search://python` and the function will be called with `query="python", max_results=10, include_archived=False`. MCP Developers can still call the underlying `search_resources` function directly with more specific parameters.

An even more powerful pattern is registering a single function with multiple URI templates, allowing different ways to access the same data:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Define a user lookup function that can be accessed by different identifiers
@mcp.resource("users://email/{email}")
@mcp.resource("users://name/{name}")
def lookup_user(name: str | None = None, email: str | None = None) -> dict:
    """Look up a user by either name or email."""
    if email:
        return find_user_by_email(email) # pseudocode
    elif name:
        return find_user_by_name(name) # pseudocode
    else:
        return {"error": "No lookup parameters provided"}
```

Now an LLM or client can retrieve user information in two different ways:
- `users://email/alice@example.com` → Looks up user by email (with name=None)
- `users://name/Bob` → Looks up user by name (with email=None)

In this stacked decorator pattern:
- The `name` parameter is only provided when using the `users://name/{name}` template
- The `email` parameter is only provided when using the `users://email/{email}` template
- Each parameter defaults to `None` when not included in the URI
- The function logic handles whichever parameter is provided

Templates provide a powerful way to expose parameterized data access points following REST-like principles.

## Error Handling

<VersionBadge version="2.4.1" />

If your resource function encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ResourceError`.

By default, all exceptions (including their details) are logged and converted into an MCP error response to be sent back to the client LLM. This helps the LLM understand failures and react appropriately.

If you want to mask internal error details for security reasons, you can:

1. Use the `mask_error_details=True` parameter when creating your `FastMCP` instance:
```python
mcp = FastMCP(name="SecureServer", mask_error_details=True)
```

2. Or use `ResourceError` to explicitly control what error information is sent to clients:
```python
from fastmcp import FastMCP
from fastmcp.exceptions import ResourceError

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://safe-error")
def fail_with_details() -> str:
    """This resource provides detailed error information."""
    # ResourceError contents are always sent back to clients,
    # regardless of mask_error_details setting
    raise ResourceError("Unable to retrieve data: file not found")

@mcp.resource("resource://masked-error")
def fail_with_masked_details() -> str:
    """This resource masks internal error details when mask_error_details=True."""
    # This message would be masked if mask_error_details=True
    raise ValueError("Sensitive internal file path: /etc/secrets.conf")

@mcp.resource("data://{id}")
def get_data_by_id(id: str) -> dict:
    """Template resources also support the same error handling pattern."""
    if id == "secure":
        raise ValueError("Cannot access secure data")
    elif id == "missing":
        raise ResourceError("Data ID 'missing' not found in database")
    return {"id": id, "value": "data"}
```

When `mask_error_details=True`, only error messages from `ResourceError` will include details, other exceptions will be converted to a generic message.

## Server Behavior

### Duplicate Resources

<VersionBadge version="2.1.0" />

You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the `on_duplicate_resources` setting during `FastMCP` initialization.

```python
from fastmcp import FastMCP

mcp = FastMCP(
    name="ResourceServer",
    on_duplicate_resources="error" # Raise error on duplicates
)

@mcp.resource("data://config")
def get_config_v1(): return {"version": 1}

# This registration attempt will raise a ValueError because
# "data://config" is already registered and the behavior is "error".
# @mcp.resource("data://config")
# def get_config_v2(): return {"version": 2}
```

The duplicate behavior options are:

-   `"warn"` (default): Logs a warning, and the new resource/template replaces the old one.
-   `"error"`: Raises a `ValueError`, preventing the duplicate registration.
-   `"replace"`: Silently replaces the existing resource/template with the new one.
-   `"ignore"`: Keeps the original resource/template and ignores the new registration attempt.

================
File: docs/servers/sampling.mdx
================
---
title: LLM Sampling
sidebarTitle: Sampling
description: Request the client's LLM to generate text based on provided messages through the MCP context.
icon: robot
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="2.0.0" />

LLM sampling allows MCP tools to request the client's LLM to generate text based on provided messages. This is useful when tools need to leverage the LLM's capabilities to process data, generate responses, or perform text-based analysis.

## Why Use LLM Sampling?

LLM sampling enables tools to:

- **Leverage AI capabilities**: Use the client's LLM for text generation and analysis
- **Offload complex reasoning**: Let the LLM handle tasks requiring natural language understanding
- **Generate dynamic content**: Create responses, summaries, or transformations based on data
- **Maintain context**: Use the same LLM instance that the user is already interacting with

### Basic Usage

Use `ctx.sample()` to request text generation from the client's LLM:

```python {14}
from fastmcp import FastMCP, Context

mcp = FastMCP("SamplingDemo")

@mcp.tool
async def analyze_sentiment(text: str, ctx: Context) -> dict:
    """Analyze the sentiment of text using the client's LLM."""
    prompt = f"""Analyze the sentiment of the following text as positive, negative, or neutral. 
    Just output a single word - 'positive', 'negative', or 'neutral'.
    
    Text to analyze: {text}"""
    
    # Request LLM analysis
    response = await ctx.sample(prompt)
    
    # Process the LLM's response
    sentiment = response.text.strip().lower()
    
    # Map to standard sentiment values
    if "positive" in sentiment:
        sentiment = "positive"
    elif "negative" in sentiment:
        sentiment = "negative"
    else:
        sentiment = "neutral"
    
    return {"text": text, "sentiment": sentiment}
```

## Method Signature

<Card icon="code" title="Context Sampling Method">
<ResponseField name="ctx.sample" type="async method">
  Request text generation from the client's LLM
  
  <Expandable title="Parameters">
    <ResponseField name="messages" type="str | list[str | SamplingMessage]">
      A string or list of strings/message objects to send to the LLM
    </ResponseField>
    
    <ResponseField name="system_prompt" type="str | None" default="None">
      Optional system prompt to guide the LLM's behavior
    </ResponseField>
    
    <ResponseField name="temperature" type="float | None" default="None">
      Optional sampling temperature (controls randomness, typically 0.0-1.0)
    </ResponseField>
    
    <ResponseField name="max_tokens" type="int | None" default="512">
      Optional maximum number of tokens to generate
    </ResponseField>
    
    <ResponseField name="model_preferences" type="ModelPreferences | str | list[str] | None" default="None">
      Optional model selection preferences (e.g., model hint string, list of hints, or ModelPreferences object)
    </ResponseField>
  </Expandable>
  
  <Expandable title="Response">
    <ResponseField name="response" type="TextContent | ImageContent">
      The LLM's response content (typically TextContent with a .text attribute)
    </ResponseField>
  </Expandable>
</ResponseField>
</Card>

## Simple Text Generation

### Basic Prompting

Generate text with simple string prompts:

```python {6}
@mcp.tool
async def generate_summary(content: str, ctx: Context) -> str:
    """Generate a summary of the provided content."""
    prompt = f"Please provide a concise summary of the following content:\n\n{content}"
    
    response = await ctx.sample(prompt)
    return response.text
```

### System Prompt

Use system prompts to guide the LLM's behavior:

```python {4-9}
@mcp.tool
async def generate_code_example(concept: str, ctx: Context) -> str:
    """Generate a Python code example for a given concept."""
    response = await ctx.sample(
        messages=f"Write a simple Python code example demonstrating '{concept}'.",
        system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
        temperature=0.7,
        max_tokens=300
    )
    
    code_example = response.text
    return f"```python\n{code_example}\n```"
```


### Model Preferences

Specify model preferences for different use cases:

```python {4-8, 17-22}
@mcp.tool
async def creative_writing(topic: str, ctx: Context) -> str:
    """Generate creative content using a specific model."""
    response = await ctx.sample(
        messages=f"Write a creative short story about {topic}",
        model_preferences="claude-3-sonnet",  # Prefer a specific model
        temperature=0.9,  # High creativity
        max_tokens=1000
    )
    
    return response.text

@mcp.tool
async def technical_analysis(data: str, ctx: Context) -> str:
    """Perform technical analysis with a reasoning-focused model."""
    response = await ctx.sample(
        messages=f"Analyze this technical data and provide insights: {data}",
        model_preferences=["claude-3-opus", "gpt-4"],  # Prefer reasoning models
        temperature=0.2,  # Low randomness for consistency
        max_tokens=800
    )
    
    return response.text
```

### Complex Message Structures

Use structured messages for more complex interactions:

```python {1, 6-10}
from fastmcp.client.sampling import SamplingMessage

@mcp.tool
async def multi_turn_analysis(user_query: str, context_data: str, ctx: Context) -> str:
    """Perform analysis using multi-turn conversation structure."""
    messages = [
        SamplingMessage(role="user", content=f"I have this data: {context_data}"),
        SamplingMessage(role="assistant", content="I can see your data. What would you like me to analyze?"),
        SamplingMessage(role="user", content=user_query)
    ]
    
    response = await ctx.sample(
        messages=messages,
        system_prompt="You are a data analyst. Provide detailed insights based on the conversation context.",
        temperature=0.3
    )
    
    return response.text
```

## Client Requirements

LLM sampling requires client support:

- Clients must implement sampling handlers to process requests
- If the client doesn't support sampling, calls to `ctx.sample()` will fail
- See [Client Sampling](/clients/sampling) for details on implementing client-side sampling handlers

================
File: docs/servers/server.mdx
================
---
title: The FastMCP Server
sidebarTitle: Overview
description: The core FastMCP server class for building MCP applications with tools, resources, and prompts.
icon: server
---

import { VersionBadge } from "/snippets/version-badge.mdx"

The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients.

## Creating a Server

Instantiating a server is straightforward. You typically provide a name for your server, which helps identify it in client applications or logs.

```python
from fastmcp import FastMCP

# Create a basic server instance
mcp = FastMCP(name="MyAssistantServer")

# You can also add instructions for how to interact with the server
mcp_with_instructions = FastMCP(
    name="HelpfulAssistant",
    instructions="""
        This server provides data analysis tools.
        Call get_average() to analyze numerical data.
    """,
)
```

The `FastMCP` constructor accepts several arguments:

<Card icon="code" title="FastMCP Constructor Parameters">
<ParamField body="name" type="str" default="FastMCP">
  A human-readable name for your server
</ParamField>

<ParamField body="instructions" type="str | None">
  Description of how to interact with this server. These instructions help clients understand the server's purpose and available functionality
</ParamField>

<ParamField body="lifespan" type="AsyncContextManager | None">
  An async context manager function for server startup and shutdown logic
</ParamField>

<ParamField body="tags" type="set[str] | None">
  A set of strings to tag the server itself
</ParamField>

<ParamField body="tools" type="list[Tool | Callable] | None">
  A list of tools (or functions to convert to tools) to add to the server. In some cases, providing tools programmatically may be more convenient than using the `@mcp.tool` decorator
</ParamField>

<ParamField body="**settings" type="Any">
  Keyword arguments corresponding to additional `ServerSettings` configuration
</ParamField>
</Card>
## Components

FastMCP servers expose several types of components to the client:

### Tools

Tools are functions that the client can call to perform actions or access external systems.

```python
@mcp.tool
def multiply(a: float, b: float) -> float:
    """Multiplies two numbers together."""
    return a * b
```

See [Tools](/servers/tools) for detailed documentation.

### Resources

Resources expose data sources that the client can read.

```python
@mcp.resource("data://config")
def get_config() -> dict:
    """Provides the application configuration."""
    return {"theme": "dark", "version": "1.0"}
```

See [Resources & Templates](/servers/resources) for detailed documentation.

### Resource Templates

Resource templates are parameterized resources that allow the client to request specific data.

```python
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: int) -> dict:
    """Retrieves a user's profile by ID."""
    # The {user_id} in the URI is extracted and passed to this function
    return {"id": user_id, "name": f"User {user_id}", "status": "active"}
```

See [Resources & Templates](/servers/resources) for detailed documentation.

### Prompts

Prompts are reusable message templates for guiding the LLM.

```python
@mcp.prompt
def analyze_data(data_points: list[float]) -> str:
    """Creates a prompt asking for analysis of numerical data."""
    formatted_data = ", ".join(str(point) for point in data_points)
    return f"Please analyze these data points: {formatted_data}"
```

See [Prompts](/servers/prompts) for detailed documentation.

## Tag-Based Filtering

<VersionBadge version="2.8.0" />

FastMCP supports tag-based filtering to selectively expose components based on configurable include/exclude tag sets. This is useful for creating different views of your server for different environments or users.

Components can be tagged when defined using the `tags` parameter:

```python
@mcp.tool(tags={"public", "utility"})
def public_tool() -> str:
    return "This tool is public"

@mcp.tool(tags={"internal", "admin"})
def admin_tool() -> str:
    return "This tool is for admins only"
```


The filtering logic works as follows:
- **Include tags**: If specified, only components with at least one matching tag are exposed
- **Exclude tags**: Components with any matching tag are filtered out
- **Precedence**: Exclude tags always take priority over include tags

<Tip>
To ensure a component is never exposed, you can set `enabled=False` on the component itself. To learn more, see the component-specific documentation.
</Tip>

You configure tag-based filtering when creating your server:

```python
# Only expose components tagged with "public"
mcp = FastMCP(include_tags={"public"})

# Hide components tagged as "internal" or "deprecated"  
mcp = FastMCP(exclude_tags={"internal", "deprecated"})

# Combine both: show admin tools but hide deprecated ones
mcp = FastMCP(include_tags={"admin"}, exclude_tags={"deprecated"})
```

This filtering applies to all component types (tools, resources, resource templates, and prompts) and affects both listing and access.

## Running the Server

FastMCP servers need a transport mechanism to communicate with clients. You typically start your server by calling the `mcp.run()` method on your `FastMCP` instance, often within an `if __name__ == "__main__":` block in your main server script. This pattern ensures compatibility with various MCP clients.

```python
# my_server.py
from fastmcp import FastMCP

mcp = FastMCP(name="MyServer")

@mcp.tool
def greet(name: str) -> str:
    """Greet a user by name."""
    return f"Hello, {name}!"

if __name__ == "__main__":
    # This runs the server, defaulting to STDIO transport
    mcp.run()
    
    # To use a different transport, e.g., Streamable HTTP:
    # mcp.run(transport="http", host="127.0.0.1", port=9000)
```

FastMCP supports several transport options: 
- STDIO (default, for local tools)
- Streamable HTTP (recommended for web services)
- SSE (legacy web transport, deprecated)

The server can also be run using the FastMCP CLI.

For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide.


## Composing Servers

<VersionBadge version="2.2.0" />

FastMCP supports composing multiple servers together using `import_server` (static copy) and `mount` (live link). This allows you to organize large applications into modular components or reuse existing servers.

See the [Server Composition](/servers/composition) guide for full details, best practices, and examples.

```python
# Example: Importing a subserver
from fastmcp import FastMCP
import asyncio

main = FastMCP(name="Main")
sub = FastMCP(name="Sub")

@sub.tool
def hello(): 
    return "hi"

# Mount directly
main.mount(sub, prefix="sub")
```

## Proxying Servers

<VersionBadge version="2.0.0" />

FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.as_proxy`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa.

See the [Proxying Servers](/servers/proxy) guide for details and advanced usage.

```python
from fastmcp import FastMCP, Client

backend = Client("http://example.com/mcp/sse")
proxy = FastMCP.as_proxy(backend, name="ProxyServer")
# Now use the proxy like any FastMCP server
```

## Server Configuration

Servers can be configured using a combination of initialization arguments, global settings, and transport-specific settings.

### Server-Specific Configuration

Server-specific settings are passed when creating the `FastMCP` instance and control server behavior:

```python
from fastmcp import FastMCP

# Configure server-specific settings
mcp = FastMCP(
    name="ConfiguredServer",
    dependencies=["requests", "pandas>=2.0.0"],  # Optional server dependencies
    include_tags={"public", "api"},              # Only expose these tagged components
    exclude_tags={"internal", "deprecated"},     # Hide these tagged components
    on_duplicate_tools="error",                  # Handle duplicate registrations
    on_duplicate_resources="warn",
    on_duplicate_prompts="replace",
)
```

### Constructor Parameters

<Card icon="code" title="AdditionalFastMCP Constructor Parameters">
<ParamField body="dependencies" type="list[str] | None">
  Optional server dependencies list with package specifications
</ParamField>

<ParamField body="include_tags" type="set[str] | None">
  Only expose components with at least one matching tag
</ParamField>

<ParamField body="exclude_tags" type="set[str] | None">
  Hide components with any matching tag
</ParamField>

<ParamField body="on_duplicate_tools" type='Literal["error", "warn", "replace"]' default="error">
  How to handle duplicate tool registrations
</ParamField>

<ParamField body="on_duplicate_resources" type='Literal["error", "warn", "replace"]' default="warn">
  How to handle duplicate resource registrations
</ParamField>

<ParamField body="on_duplicate_prompts" type='Literal["error", "warn", "replace"]' default="replace">
  How to handle duplicate prompt registrations
</ParamField>
</Card>

### Global Settings

Global settings affect all FastMCP servers and can be configured via environment variables (prefixed with `FASTMCP_`) or in a `.env` file:

```python
import fastmcp

# Access global settings
print(fastmcp.settings.log_level)        # Default: "INFO"
print(fastmcp.settings.mask_error_details)  # Default: False
print(fastmcp.settings.resource_prefix_format)  # Default: "path"
```

Common global settings include:
- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL`
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
- **`resource_prefix_format`**: How to format resource prefixes ("path" or "protocol"), set with `FASTMCP_RESOURCE_PREFIX_FORMAT`

### Transport-Specific Configuration

Transport settings are provided when running the server and control network behavior:

```python
# Configure transport when running
mcp.run(
    transport="http",
    host="0.0.0.0",           # Bind to all interfaces
    port=9000,                # Custom port
    log_level="DEBUG",        # Override global log level
)

# Or for async usage
await mcp.run_async(
    transport="http", 
    host="127.0.0.1",
    port=8080,
)
```

### Environment Variables

Settings can be configured via environment variables:

```bash
# Global settings
export FASTMCP_LOG_LEVEL=DEBUG
export FASTMCP_MASK_ERROR_DETAILS=True
export FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
```

### Custom Tool Serialization

<VersionBadge version="2.2.7" />

By default, FastMCP serializes tool return values to JSON when they need to be converted to text. You can customize this behavior by providing a `tool_serializer` function when creating your server:

```python
import yaml
from fastmcp import FastMCP

# Define a custom serializer that formats dictionaries as YAML
def yaml_serializer(data):
    return yaml.dump(data, sort_keys=False)

# Create a server with the custom serializer
mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer)

@mcp.tool
def get_config():
    """Returns configuration in YAML format."""
    return {"api_key": "abc123", "debug": True, "rate_limit": 100}
```

The serializer function takes any data object and returns a string representation. This is applied to **all non-string return values** from your tools. Tools that already return strings bypass the serializer.

This customization is useful when you want to:
- Format data in a specific way (like YAML or custom formats)
- Control specific serialization options (like indentation or sorting)
- Add metadata or transform data before sending it to clients

<Tip>
If the serializer function raises an exception, the tool will fall back to the default JSON serialization to avoid breaking the server.
</Tip>

================
File: docs/servers/tools.mdx
================
---
title: Tools
sidebarTitle: Tools
description: Expose functions as executable capabilities for your MCP client.
icon: wrench
---

import { VersionBadge } from '/snippets/version-badge.mdx'

Tools are the core building blocks that allow your LLM to interact with external systems, execute code, and access data that isn't in its training data. In FastMCP, tools are Python functions exposed to LLMs through the MCP protocol.

## What Are Tools?

Tools in FastMCP transform regular Python functions into capabilities that LLMs can invoke during conversations. When an LLM decides to use a tool:

1.  It sends a request with parameters based on the tool's schema.
2.  FastMCP validates these parameters against your function's signature.
3.  Your function executes with the validated inputs.
4.  The result is returned to the LLM, which can use it in its response.

This allows LLMs to perform tasks like querying databases, calling APIs, making calculations, or accessing files—extending their capabilities beyond what's in their training data.

## Tools

### The `@tool` Decorator

Creating a tool is as simple as decorating a Python function with `@mcp.tool`:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="CalculatorServer")

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b
```

When this tool is registered, FastMCP automatically:
- Uses the function name (`add`) as the tool name.
- Uses the function's docstring (`Adds two integer numbers...`) as the tool description.
- Generates an input schema based on the function's parameters and type annotations.
- Handles parameter validation and error reporting.

The way you define your Python function dictates how the tool appears and behaves for the LLM client.

<Tip>
Functions with `*args` or `**kwargs` are not supported as tools. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists.
</Tip>

#### Decorator Arguments

While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.tool` decorator:

```python
@mcp.tool(
    name="find_products",           # Custom tool name for the LLM
    description="Search the product catalog with optional category filtering.", # Custom description
    tags={"catalog", "search"},      # Optional tags for organization/filtering
)
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
    """Internal function description (ignored if description is provided above)."""
    # Implementation...
    print(f"Searching for '{query}' in category '{category}'")
    return [{"id": 2, "name": "Another Product"}]
```

<Card icon="code" title="@tool Decorator Arguments">
<ParamField body="name" type="str | None">
  Sets the explicit tool name exposed via MCP. If not provided, uses the function name
</ParamField>

<ParamField body="description" type="str | None">
  Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose
</ParamField>

<ParamField body="tags" type="set[str] | None">
  A set of strings to categorize the tool. Clients might use tags to filter or group available tools
</ParamField>

<ParamField body="enabled" type="bool" default="True">
  A boolean to enable or disable the tool. See [Disabling Tools](#disabling-tools) for more information
</ParamField>

<ParamField body="exclude_args" type="list[str] | None">
  A list of argument names to exclude from the tool schema shown to the LLM. See [Excluding Arguments](#excluding-arguments) for more information
</ParamField>

<ParamField body="annotations" type="ToolAnnotations | dict | None">
    An optional `ToolAnnotations` object or dictionary to add additional metadata about the tool.
  <Expandable title="ToolAnnotations attributes">
    <ParamField body="title" type="str | None">
      A human-readable title for the tool.
    </ParamField>
    <ParamField body="readOnlyHint" type="bool | None">
      If true, the tool does not modify its environment.
    </ParamField>
    <ParamField body="destructiveHint" type="bool | None">
      If true, the tool may perform destructive updates to its environment.
    </ParamField>
    <ParamField body="idempotentHint" type="bool | None">
      If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment.
    </ParamField>
    <ParamField body="openWorldHint" type="bool | None">
      If true, this tool may interact with an "open world" of external entities. If false, the tool's domain of interaction is closed.
    </ParamField>
  </Expandable>
</ParamField>
</Card>
### Tool Parameters

#### Type Annotations

Type annotations for parameters are essential for proper tool functionality. They:
1. Inform the LLM about the expected data types for each parameter
2. Enable FastMCP to validate input data from clients
3. Generate accurate JSON schemas for the MCP protocol

Use standard Python type annotations for parameters:

```python
@mcp.tool
def analyze_text(
    text: str,
    max_tokens: int = 100,
    language: str | None = None
) -> dict:
    """Analyze the provided text."""
    # Implementation...
```

#### Parameter Metadata

You can provide additional metadata about parameters using Pydantic's `Field` class with `Annotated`. This approach is preferred as it's more modern and keeps type hints separate from validation rules:

```python
from typing import Annotated
from pydantic import Field

@mcp.tool
def process_image(
    image_url: Annotated[str, Field(description="URL of the image to process")],
    resize: Annotated[bool, Field(description="Whether to resize the image")] = False,
    width: Annotated[int, Field(description="Target width in pixels", ge=1, le=2000)] = 800,
    format: Annotated[
        Literal["jpeg", "png", "webp"], 
        Field(description="Output image format")
    ] = "jpeg"
) -> dict:
    """Process an image with optional resizing."""
    # Implementation...
```


You can also use the Field as a default value, though the Annotated approach is preferred:

```python
@mcp.tool
def search_database(
    query: str = Field(description="Search query string"),
    limit: int = Field(10, description="Maximum number of results", ge=1, le=100)
) -> list:
    """Search the database with the provided query."""
    # Implementation...
```

Field provides several validation and documentation features:
- `description`: Human-readable explanation of the parameter (shown to LLMs)
- `ge`/`gt`/`le`/`lt`: Greater/less than (or equal) constraints
- `min_length`/`max_length`: String or collection length constraints
- `pattern`: Regex pattern for string validation
- `default`: Default value if parameter is omitted

#### Supported Types

FastMCP supports a wide range of type annotations, including all Pydantic types:

| Type Annotation         | Example                       | Description                         |
| :---------------------- | :---------------------------- | :---------------------------------- |
| Basic types             | `int`, `float`, `str`, `bool` | Simple scalar values - see [Built-in Types](#built-in-types) |
| Binary data             | `bytes`                       | Binary content - see [Binary Data](#binary-data) |
| Date and Time           | `datetime`, `date`, `timedelta` | Date and time objects - see [Date and Time Types](#date-and-time-types) |
| Collection types        | `list[str]`, `dict[str, int]`, `set[int]` | Collections of items - see [Collection Types](#collection-types) |
| Optional types          | `float \| None`, `Optional[float]`| Parameters that may be null/omitted - see [Union and Optional Types](#union-and-optional-types) |
| Union types             | `str \| int`, `Union[str, int]`| Parameters accepting multiple types - see [Union and Optional Types](#union-and-optional-types) |
| Constrained types       | `Literal["A", "B"]`, `Enum`   | Parameters with specific allowed values - see [Constrained Types](#constrained-types) |
| Paths                   | `Path`                        | File system paths - see [Paths](#paths) |
| UUIDs                   | `UUID`                        | Universally unique identifiers - see [UUIDs](#uuids) |
| Pydantic models         | `UserData`                    | Complex structured data - see [Pydantic Models](#pydantic-models) |

For additional type annotations not listed here, see the [Parameter Types](#parameter-types) section below for more detailed information and examples.

#### Optional Arguments

FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.

```python
@mcp.tool
def search_products(
    query: str,                   # Required - no default value
    max_results: int = 10,        # Optional - has default value
    sort_by: str = "relevance",   # Optional - has default value
    category: str | None = None   # Optional - can be None
) -> list[dict]:
    """Search the product catalog."""
    # Implementation...
```

In this example, the LLM must provide a `query` parameter, while `max_results`, `sort_by`, and `category` will use their default values if not explicitly provided.


### Excluding Arguments

<VersionBadge version="2.6.0" /> 

You can exclude certain arguments from the tool schema shown to the LLM. This is useful for arguments that are injected at runtime (such as `state`, `user_id`, or credentials) and should not be exposed to the LLM or client. Only arguments with default values can be excluded; attempting to exclude a required argument will raise an error.

Example:

```python
@mcp.tool(
    name="get_user_details",
    exclude_args=["user_id"]
)
def get_user_details(user_id: str = None) -> str:
    # user_id will be injected by the server, not provided by the LLM
    ...
```

With this configuration, `user_id` will not appear in the tool's parameter schema, but can still be set by the server or framework at runtime.

For more complex tool transformations, see [Transforming Tools](/patterns/tool-transformation).

### Disabling Tools

<VersionBadge version="2.8.0" />

You can control the visibility and availability of tools by enabling or disabling them. This is useful for feature flagging, maintenance, or dynamically changing the toolset available to a client. Disabled tools will not appear in the list of available tools returned by `list_tools`, and attempting to call a disabled tool will result in an "Unknown tool" error, just as if the tool did not exist.

By default, all tools are enabled. You can disable a tool upon creation using the `enabled` parameter in the decorator:

```python
@mcp.tool(enabled=False)
def maintenance_tool():
    """This tool is currently under maintenance."""
    return "This tool is disabled."
```

You can also toggle a tool's state programmatically after it has been created:

```python
@mcp.tool
def dynamic_tool():
    return "I am a dynamic tool."

# Disable and re-enable the tool
dynamic_tool.disable()
dynamic_tool.enable()
```

### Async Tools

FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as tools.

```python
# Synchronous tool (suitable for CPU-bound or quick tasks)
@mcp.tool
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Calculate the distance between two coordinates."""
    # Implementation...
    return 42.5

# Asynchronous tool (ideal for I/O-bound operations)
@mcp.tool
async def fetch_weather(city: str) -> dict:
    """Retrieve current weather conditions for a city."""
    # Use 'async def' for operations involving network calls, file I/O, etc.
    # This prevents blocking the server while waiting for external operations.
    async with aiohttp.ClientSession() as session:
        async with session.get(f"https://api.example.com/weather/{city}") as response:
            # Check response status before returning
            response.raise_for_status()
            return await response.json()
```

Use `async def` when your tool needs to perform operations that might wait for external systems (network requests, database queries, file access) to keep your server responsive.

### Return Values


FastMCP tools can return data in two complementary formats: **traditional content blocks** (like text and images) and **structured outputs** (machine-readable JSON). When you add return type annotations, FastMCP automatically generates **output schemas** to validate the structured data and enables clients to deserialize results back to Python objects.

Understanding how these three concepts work together:

- **Return Values**: What your Python function returns (determines both content blocks and structured data)
- **Structured Outputs**: JSON data sent alongside traditional content for machine processing  
- **Output Schemas**: JSON Schema declarations that describe and validate the structured output format

The following sections explain each concept in detail.

#### Content Blocks

FastMCP automatically converts tool return values into appropriate MCP content blocks:

- **`str`**: Sent as `TextContent`
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (within an `EmbeddedResource`)
- **`fastmcp.utilities.types.Image`**: Sent as `ImageContent`
- **`fastmcp.utilities.types.Audio`**: Sent as `AudioContent`
- **`fastmcp.utilities.types.File`**: Sent as base64-encoded `EmbeddedResource`
- **A list of any of the above**: Converts each item appropriately
- **`None`**: Results in an empty response

#### Structured Output

<VersionBadge version="2.10.0" />

The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content) structured content, which is a new way to return data from tools. Structured content is a JSON object that is sent alongside traditional content. FastMCP automatically creates structured outputs alongside traditional content when your tool returns data that has a JSON object representation. This provides machine-readable JSON data that clients can deserialize back to Python objects.

**Automatic Structured Content Rules:**
- **Object-like results** (`dict`, Pydantic models, dataclasses) → Always become structured content (even without output schema)  
- **Non-object results** (`int`, `str`, `list`) → Only become structured content if there's an output schema to validate/serialize them
- **All results** → Always become traditional content blocks for backward compatibility

<Note>
This automatic behavior enables clients to receive machine-readable data alongside human-readable content without requiring explicit output schemas for object-like returns.
</Note>

##### Object-like Results (Automatic Structured Content)

<CodeGroup>
```python Dict Return (No Schema Needed)
@mcp.tool
def get_user_data(user_id: str) -> dict:
    """Get user data without type annotation."""
    return {"name": "Alice", "age": 30, "active": True}
```

```json Traditional Content
"{\n  \"name\": \"Alice\",\n  \"age\": 30,\n  \"active\": true\n}"
```

```json Structured Content (Automatic)
{
  "name": "Alice", 
  "age": 30,
  "active": true
}
```
</CodeGroup>

##### Non-object Results (Schema Required)

<CodeGroup>
```python Integer Return (No Schema)
@mcp.tool  
def calculate_sum(a: int, b: int):
    """Calculate sum without return annotation."""
    return a + b  # Returns 8
```

```json Traditional Content Only
"8"
```

```python Integer Return (With Schema)
@mcp.tool
def calculate_sum(a: int, b: int) -> int:
    """Calculate sum with return annotation."""  
    return a + b  # Returns 8
```

```json Traditional Content
"8"
```

```json Structured Content (From Schema)
{
  "result": 8
}
```
</CodeGroup>

##### Complex Type Example

<CodeGroup>
```python Tool Definition
from dataclasses import dataclass
from fastmcp import FastMCP

mcp = FastMCP()

@dataclass
class Person:
    name: str
    age: int
    email: str

@mcp.tool
def get_user_profile(user_id: str) -> Person:
    """Get a user's profile information."""
    return Person(name="Alice", age=30, email="alice@example.com")
```

```json Generated Output Schema
{
  "properties": {
    "name": {"title": "Name", "type": "string"},
    "age": {"title": "Age", "type": "integer"}, 
    "email": {"title": "Email", "type": "string"}
  },
  "required": ["name", "age", "email"],
  "title": "Person",
  "type": "object"
}
```

```json Structured Output
{
  "name": "Alice",
  "age": 30,
  "email": "alice@example.com"
}
```
</CodeGroup>

#### Output Schemas

<VersionBadge version="2.10.0" />

The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) output schemas, which are a new way to describe the expected output format of a tool. When an output schema is provided, the tool *must* return structured output that matches the schema.

When you add return type annotations to your functions, FastMCP automatically generates JSON schemas that describe the expected output format. These schemas help MCP clients understand and validate the structured data they receive.

##### Primitive Type Wrapping

For primitive return types (like `int`, `str`, `bool`), FastMCP automatically wraps the result under a `"result"` key to create valid structured output:

<CodeGroup>
```python Primitive Return Type
@mcp.tool
def calculate_sum(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b
```

```json Generated Schema (Wrapped)
{
  "type": "object",
  "properties": {
    "result": {"type": "integer"}
  },
  "x-fastmcp-wrap-result": true
}
```

```json Structured Output
{
  "result": 8
}
```
</CodeGroup>

##### Manual Schema Control

You can override the automatically generated schema by providing a custom `output_schema`:

```python
@mcp.tool(output_schema={
    "type": "object", 
    "properties": {
        "data": {"type": "string"},
        "metadata": {"type": "object"}
    }
})
def custom_schema_tool() -> dict:
    """Tool with custom output schema."""
    return {"data": "Hello", "metadata": {"version": "1.0"}}
```

Schema generation works for most common types including basic types, collections, union types, Pydantic models, TypedDict structures, and dataclasses.

<Warning>
**Important Constraints**: 
- Output schemas must be object types (`"type": "object"`)
- If you provide an output schema, your tool **must** return structured output that matches it
- However, you can provide structured output without an output schema (using `ToolResult`)
</Warning>

#### Full Control with ToolResult

For complete control over both traditional content and structured output, return a `ToolResult` object:

```python
from fastmcp.tools.tool import ToolResult

@mcp.tool
def advanced_tool() -> ToolResult:
    """Tool with full control over output."""
    return ToolResult(
        content=[TextContent(text="Human-readable summary")],
        structured_content={"data": "value", "count": 42}
    )
```

When returning `ToolResult`:
- You control exactly what content and structured data is sent
- Output schemas are optional - structured content can be provided without a schema
- Clients receive both traditional content blocks and structured data

<Note>
If your return type annotation cannot be converted to a JSON schema (e.g., complex custom classes without Pydantic support), the output schema will be omitted but the tool will still function normally with traditional content.
</Note>

### Error Handling

<VersionBadge version="2.4.1" />

If your tool encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ToolError`.

By default, all exceptions (including their details) are logged and converted into an MCP error response to be sent back to the client LLM. This helps the LLM understand failures and react appropriately.

If you want to mask internal error details for security reasons, you can:

1. Use the `mask_error_details=True` parameter when creating your `FastMCP` instance:
```python
mcp = FastMCP(name="SecureServer", mask_error_details=True)
```

2. Or use `ToolError` to explicitly control what error information is sent to clients:
```python
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError

@mcp.tool
def divide(a: float, b: float) -> float:
    """Divide a by b."""

    if b == 0:
        # Error messages from ToolError are always sent to clients,
        # regardless of mask_error_details setting
        raise ToolError("Division by zero is not allowed.")
    
    # If mask_error_details=True, this message would be masked
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("Both arguments must be numbers.")
        
    return a / b
```

When `mask_error_details=True`, only error messages from `ToolError` will include details, other exceptions will be converted to a generic message.

### Annotations

<VersionBadge version="2.2.7" />

FastMCP allows you to add specialized metadata to your tools through annotations. These annotations communicate how tools behave to client applications without consuming token context in LLM prompts.

Annotations serve several purposes in client applications:
- Adding user-friendly titles for display purposes
- Indicating whether tools modify data or systems
- Describing the safety profile of tools (destructive vs. non-destructive)
- Signaling if tools interact with external systems

You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool` decorator:

```python
@mcp.tool(
    annotations={
        "title": "Calculate Sum",
        "readOnlyHint": True,
        "openWorldHint": False
    }
)
def calculate_sum(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b
```

FastMCP supports these standard annotations:

| Annotation | Type | Default | Purpose |
| :--------- | :--- | :------ | :------ |
| `title` | string | - | Display name for user interfaces |
| `readOnlyHint` | boolean | false | Indicates if the tool only reads without making changes |
| `destructiveHint` | boolean | true | For non-readonly tools, signals if changes are destructive |
| `idempotentHint` | boolean | false | Indicates if repeated identical calls have the same effect as a single call |
| `openWorldHint` | boolean | true | Specifies if the tool interacts with external systems |

Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and safety controls, but won't enforce security boundaries on their own. Always focus on making your annotations accurately represent what your tool actually does.

### Notifications

<VersionBadge version="2.9.1" />

FastMCP automatically sends `notifications/tools/list_changed` notifications to connected clients when tools are added, removed, enabled, or disabled. This allows clients to stay up-to-date with the current tool set without manually polling for changes.

```python
@mcp.tool
def example_tool() -> str:
    return "Hello!"

# These operations trigger notifications:
mcp.add_tool(example_tool)     # Sends tools/list_changed notification
example_tool.disable()         # Sends tools/list_changed notification  
example_tool.enable()          # Sends tools/list_changed notification
mcp.remove_tool("example_tool") # Sends tools/list_changed notification
```

Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their tool lists or update their interfaces.

## MCP Context

Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.

```python
from fastmcp import FastMCP, Context

mcp = FastMCP(name="ContextDemo")

@mcp.tool
async def process_data(data_uri: str, ctx: Context) -> dict:
    """Process data from a resource with progress reporting."""
    await ctx.info(f"Processing data from {data_uri}")
    
    # Read a resource
    resource = await ctx.read_resource(data_uri)
    data = resource[0].content if resource else ""
    
    # Report progress
    await ctx.report_progress(progress=50, total=100)
    
    # Example request to the client's LLM for help
    summary = await ctx.sample(f"Summarize this in 10 words: {data[:200]}")
    
    await ctx.report_progress(progress=100, total=100)
    return {
        "length": len(data),
        "summary": summary.text
    }
```

The Context object provides access to:

- **Logging**: `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`
- **Progress Reporting**: `ctx.report_progress(progress, total)`
- **Resource Access**: `ctx.read_resource(uri)`
- **LLM Sampling**: `ctx.sample(...)`
- **Request Information**: `ctx.request_id`, `ctx.client_id`

For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).

## Parameter Types

FastMCP supports a wide variety of parameter types to give you flexibility when designing your tools.

FastMCP generally supports all types that Pydantic supports as fields, including all Pydantic custom types. This means you can use any type that can be validated and parsed by Pydantic in your tool parameters.

FastMCP supports **type coercion** when possible. This means that if a client sends data that doesn't match the expected type, FastMCP will attempt to convert it to the appropriate type. For example, if a client sends a string for a parameter annotated as `int`, FastMCP will attempt to convert it to an integer. If the conversion is not possible, FastMCP will return a validation error.

### Built-in Types

The most common parameter types are Python's built-in scalar types:

```python
@mcp.tool
def process_values(
    name: str,             # Text data
    count: int,            # Integer numbers
    amount: float,         # Floating point numbers
    enabled: bool          # Boolean values (True/False)
):
    """Process various value types."""
    # Implementation...
```

These types provide clear expectations to the LLM about what values are acceptable and allow FastMCP to validate inputs properly. Even if a client provides a string like "42", it will be coerced to an integer for parameters annotated as `int`.

### Date and Time Types

FastMCP supports various date and time types from the `datetime` module:

```python
from datetime import datetime, date, timedelta

@mcp.tool
def process_date_time(
    event_date: date,             # ISO format date string or date object
    event_time: datetime,         # ISO format datetime string or datetime object
    duration: timedelta = timedelta(hours=1)  # Integer seconds or timedelta
) -> str:
    """Process date and time information."""
    # Types are automatically converted from strings
    assert isinstance(event_date, date)  
    assert isinstance(event_time, datetime)
    assert isinstance(duration, timedelta)
    
    return f"Event on {event_date} at {event_time} for {duration}"
```

- `datetime` - Accepts ISO format strings (e.g., "2023-04-15T14:30:00")
- `date` - Accepts ISO format date strings (e.g., "2023-04-15")
- `timedelta` - Accepts integer seconds or timedelta objects

### Collection Types

FastMCP supports all standard Python collection types:

```python
@mcp.tool
def analyze_data(
    values: list[float],           # List of numbers
    properties: dict[str, str],    # Dictionary with string keys and values
    unique_ids: set[int],          # Set of unique integers
    coordinates: tuple[float, float],  # Tuple with fixed structure
    mixed_data: dict[str, list[int]] # Nested collections
):
    """Analyze collections of data."""
    # Implementation...
```

All collection types can be used as parameter annotations:
- `list[T]` - Ordered sequence of items
- `dict[K, V]` - Key-value mapping
- `set[T]` - Unordered collection of unique items
- `tuple[T1, T2, ...]` - Fixed-length sequence with potentially different types

Collection types can be nested and combined to represent complex data structures. JSON strings that match the expected structure will be automatically parsed and converted to the appropriate Python collection type.

### Union and Optional Types

For parameters that can accept multiple types or may be omitted:

```python
@mcp.tool
def flexible_search(
    query: str | int,              # Can be either string or integer
    filters: dict[str, str] | None = None,  # Optional dictionary
    sort_field: str | None = None  # Optional string
):
    """Search with flexible parameter types."""
    # Implementation...
```

Modern Python syntax (`str | int`) is preferred over older `Union[str, int]` forms. Similarly, `str | None` is preferred over `Optional[str]`.

### Constrained Types

When a parameter must be one of a predefined set of values, you can use either Literal types or Enums:

#### Literals

Literals constrain parameters to a specific set of values:

```python
from typing import Literal

@mcp.tool
def sort_data(
    data: list[float],
    order: Literal["ascending", "descending"] = "ascending",
    algorithm: Literal["quicksort", "mergesort", "heapsort"] = "quicksort"
):
    """Sort data using specific options."""
    # Implementation...
```

Literal types:
- Specify exact allowable values directly in the type annotation
- Help LLMs understand exactly which values are acceptable
- Provide input validation (errors for invalid values)
- Create clear schemas for clients

#### Enums

For more structured sets of constrained values, use Python's Enum class:

```python
from enum import Enum

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

@mcp.tool
def process_image(
    image_path: str, 
    color_filter: Color = Color.RED
):
    """Process an image with a color filter."""
    # Implementation...
    # color_filter will be a Color enum member
```

When using Enum types:
- Clients should provide the enum's value (e.g., "red"), not the enum member name (e.g., "RED")
- FastMCP automatically coerces the string value into the appropriate Enum object
- Your function receives the actual Enum member (e.g., `Color.RED`)
- Validation errors are raised for values not in the enum

### Binary Data

There are two approaches to handling binary data in tool parameters:

#### Bytes

```python
@mcp.tool
def process_binary(data: bytes):
    """Process binary data directly.
    
    The client can send a binary string, which will be 
    converted directly to bytes.
    """
    # Implementation using binary data
    data_length = len(data)
    # ...
```

When you annotate a parameter as `bytes`, FastMCP will:
- Convert raw strings directly to bytes
- Validate that the input can be properly represented as bytes

FastMCP does not automatically decode base64-encoded strings for bytes parameters. If you need to accept base64-encoded data, you should handle the decoding manually as shown below.

#### Base64-encoded strings

```python
from typing import Annotated
from pydantic import Field

@mcp.tool
def process_image_data(
    image_data: Annotated[str, Field(description="Base64-encoded image data")]
):
    """Process an image from base64-encoded string.
    
    The client is expected to provide base64-encoded data as a string.
    You'll need to decode it manually.
    """
    # Manual base64 decoding
    import base64
    binary_data = base64.b64decode(image_data)
    # Process binary_data...
```

This approach is recommended when you expect to receive base64-encoded binary data from clients.

### Paths

The `Path` type from the `pathlib` module can be used for file system paths:

```python
from pathlib import Path

@mcp.tool
def process_file(path: Path) -> str:
    """Process a file at the given path."""
    assert isinstance(path, Path)  # Path is properly converted
    return f"Processing file at {path}"
```

When a client sends a string path, FastMCP automatically converts it to a `Path` object.

### UUIDs

The `UUID` type from the `uuid` module can be used for unique identifiers:

```python
import uuid

@mcp.tool
def process_item(
    item_id: uuid.UUID  # String UUID or UUID object
) -> str:
    """Process an item with the given UUID."""
    assert isinstance(item_id, uuid.UUID)  # Properly converted to UUID
    return f"Processing item {item_id}"
```

When a client sends a string UUID (e.g., "123e4567-e89b-12d3-a456-426614174000"), FastMCP automatically converts it to a `UUID` object.

### Pydantic Models

For complex, structured data with nested fields and validation, use Pydantic models:

```python
from pydantic import BaseModel, Field
from typing import Optional

class User(BaseModel):
    username: str
    email: str = Field(description="User's email address")
    age: int | None = None
    is_active: bool = True

@mcp.tool
def create_user(user: User):
    """Create a new user in the system."""
    # The input is automatically validated against the User model
    # Even if provided as a JSON string or dict
    # Implementation...
```

Using Pydantic models provides:
- Clear, self-documenting structure for complex inputs
- Built-in data validation
- Automatic generation of detailed JSON schemas for the LLM
- Automatic conversion from dict/JSON input

Clients can provide data for Pydantic model parameters as either:
- A JSON object (string)
- A dictionary with the appropriate structure
- Nested parameters in the appropriate format

### Pydantic Fields

FastMCP supports robust parameter validation through Pydantic's `Field` class. This is especially useful to ensure that input values meet specific requirements beyond just their type.

Note that fields can be used *outside* Pydantic models to provide metadata and validation constraints. The preferred approach is using `Annotated` with `Field`:

```python
from typing import Annotated
from pydantic import Field

@mcp.tool
def analyze_metrics(
    # Numbers with range constraints
    count: Annotated[int, Field(ge=0, le=100)],         # 0 <= count <= 100
    ratio: Annotated[float, Field(gt=0, lt=1.0)],       # 0 < ratio < 1.0
    
    # String with pattern and length constraints
    user_id: Annotated[str, Field(
        pattern=r"^[A-Z]{2}\d{4}$",                     # Must match regex pattern
        description="User ID in format XX0000"
    )],
    
    # String with length constraints
    comment: Annotated[str, Field(min_length=3, max_length=500)] = "",
    
    # Numeric constraints
    factor: Annotated[int, Field(multiple_of=5)] = 10,  # Must be multiple of 5
):
    """Analyze metrics with validated parameters."""
    # Implementation...
```

You can also use `Field` as a default value, though the `Annotated` approach is preferred:

```python
@mcp.tool
def validate_data(
    # Value constraints
    age: int = Field(ge=0, lt=120),                     # 0 <= age < 120
    
    # String constraints
    email: str = Field(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$"),  # Email pattern
    
    # Collection constraints
    tags: list[str] = Field(min_length=1, max_length=10)  # 1-10 tags
):
    """Process data with field validations."""
    # Implementation...
```

Common validation options include:

| Validation | Type | Description |
| :--------- | :--- | :---------- |
| `ge`, `gt` | Number | Greater than (or equal) constraint |
| `le`, `lt` | Number | Less than (or equal) constraint |
| `multiple_of` | Number | Value must be a multiple of this number |
| `min_length`, `max_length` | String, List, etc. | Length constraints |
| `pattern` | String | Regular expression pattern constraint |
| `description` | Any | Human-readable description (appears in schema) |

When a client sends invalid data, FastMCP will return a validation error explaining why the parameter failed validation.

## Server Behavior

### Duplicate Tools

<VersionBadge version="2.1.0" />

You can control how the FastMCP server behaves if you try to register multiple tools with the same name. This is configured using the `on_duplicate_tools` argument when creating the `FastMCP` instance.

```python
from fastmcp import FastMCP

mcp = FastMCP(
    name="StrictServer",
    # Configure behavior for duplicate tool names
    on_duplicate_tools="error"
)

@mcp.tool
def my_tool(): return "Version 1"

# This will now raise a ValueError because 'my_tool' already exists
# and on_duplicate_tools is set to "error".
# @mcp.tool
# def my_tool(): return "Version 2"
```

The duplicate behavior options are:

-   `"warn"` (default): Logs a warning and the new tool replaces the old one.
-   `"error"`: Raises a `ValueError`, preventing the duplicate registration.
-   `"replace"`: Silently replaces the existing tool with the new one.
-   `"ignore"`: Keeps the original tool and ignores the new registration attempt.

### Removing Tools

<VersionBadge version="2.3.4" />

You can dynamically remove tools from a server using the `remove_tool` method:

```python
from fastmcp import FastMCP

mcp = FastMCP(name="DynamicToolServer")

@mcp.tool
def calculate_sum(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

mcp.remove_tool("calculate_sum")
```

================
File: docs/snippets/version-badge.mdx
================
export const VersionBadge = ({ version }) => {
    return (
        <code className="version-badge-container">
            <p className="version-badge">
                <span className="version-badge-label">New in version:</span>&nbsp;
                <code className="version-badge-version">{version}</code>
            </p>
        </code>


    );
};

================
File: docs/snippets/youtube-embed.mdx
================
export const YouTubeEmbed = ({ videoId, title }) => {
    return (
        <iframe
            className="w-full aspect-video rounded-md"
            src={`https://www.youtube.com/embed/${videoId}`}
            title={title}
            frameBorder="0"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            allowFullScreen
        />
    );
};

================
File: docs/tutorials/create-mcp-server.mdx
================
---
title: "How to Create an MCP Server in Python"
sidebarTitle: "Creating an MCP Server"
description: "A step-by-step guide to building a Model Context Protocol (MCP) server using Python and FastMCP, from basic tools to dynamic resources."
icon: server
---

So you want to build a Model Context Protocol (MCP) server in Python. The goal is to create a service that can provide tools and data to AI models like Claude, Gemini, or others that support the protocol. While the [MCP specification](https://modelcontextprotocol.io/specification/) is powerful, implementing it from scratch involves a lot of boilerplate: handling JSON-RPC, managing session state, and correctly formatting requests and responses.

This is where **FastMCP** comes in. It's a high-level framework that handles all the protocol complexities for you, letting you focus on what matters: writing the Python functions that power your server.

This guide will walk you through creating a fully-featured MCP server from scratch using FastMCP.

<Tip>
Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out.
</Tip>

### Prerequisites

Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation).

```bash
pip install fastmcp
```


## Step 1: Create the Basic Server

Every FastMCP application starts with an instance of the `FastMCP` class. This object acts as the container for all your tools and resources.

Create a new file called `my_mcp_server.py`:

```python my_mcp_server.py
from fastmcp import FastMCP

# Create a server instance with a descriptive name
mcp = FastMCP(name="My First MCP Server")
```

That's it! You have a valid (though empty) MCP server. Now, let's add some functionality.

## Step 2: Add a Tool

Tools are functions that an LLM can execute. Let's create a simple tool that adds two numbers.

To do this, simply write a standard Python function and decorate it with `@mcp.tool`.

```python my_mcp_server.py {5-8}
from fastmcp import FastMCP

mcp = FastMCP(name="My First MCP Server")

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b
```

FastMCP automatically handles the rest:
- **Tool Name:** It uses the function name (`add`) as the tool's name.
- **Description:** It uses the function's docstring as the tool's description for the LLM.
- **Schema:** It inspects the type hints (`a: int`, `b: int`) to generate a JSON schema for the inputs.

This is the core philosophy of FastMCP: **write Python, not protocol boilerplate.**

## Step 3: Expose Data with Resources

Resources provide read-only data to the LLM. You can define a resource by decorating a function with `@mcp.resource`, providing a unique URI.

Let's expose a simple configuration dictionary as a resource.

```python my_mcp_server.py {10-13}
from fastmcp import FastMCP

mcp = FastMCP(name="My First MCP Server")

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b

@mcp.resource("resource://config")
def get_config() -> dict:
    """Provides the application's configuration."""
    return {"version": "1.0", "author": "MyTeam"}
```

When a client requests the URI `resource://config`, FastMCP will execute the `get_config` function and return its output (serialized as JSON) to the client. The function is only called when the resource is requested, enabling lazy-loading of data.

## Step 4: Generate Dynamic Content with Resource Templates

Sometimes, you need to generate resources based on parameters. This is what **Resource Templates** are for. You define them using the same `@mcp.resource` decorator but with placeholders in the URI.

Let's create a template that provides a personalized greeting.

```python my_mcp_server.py {15-17}
from fastmcp import FastMCP

mcp = FastMCP(name="My First MCP Server")

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b

@mcp.resource("resource://config")
def get_config() -> dict:
    """Provides the application's configuration."""
    return {"version": "1.0", "author": "MyTeam"}

@mcp.resource("greetings://{name}")
def personalized_greeting(name: str) -> str:
    """Generates a personalized greeting for the given name."""
    return f"Hello, {name}! Welcome to the MCP server."
```

Now, clients can request dynamic URIs:
- `greetings://Ford` will call `personalized_greeting(name="Ford")`.
- `greetings://Marvin` will call `personalized_greeting(name="Marvin")`.

FastMCP automatically maps the `{name}` placeholder in the URI to the `name` parameter in your function.

## Step 5: Run the Server

To make your server executable, add a `__main__` block to your script that calls `mcp.run()`.

```python my_mcp_server.py {19-20}
from fastmcp import FastMCP

mcp = FastMCP(name="My First MCP Server")

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b

@mcp.resource("resource://config")
def get_config() -> dict:
    """Provides the application's configuration."""
    return {"version": "1.0", "author": "MyTeam"}

@mcp.resource("greetings://{name}")
def personalized_greeting(name: str) -> str:
    """Generates a personalized greeting for the given name."""
    return f"Hello, {name}! Welcome to the MCP server."

if __name__ == "__main__":
    mcp.run()
```

Now you can run your server from the command line:
```bash
python my_mcp_server.py
```
This starts the server using the default **STDIO transport**, which is how clients like Claude Desktop communicate with local servers. To learn about other transports, like HTTP, see the [Running Your Server](/deployment/running-server) guide.

## The Complete Server

Here is the full code for `my_mcp_server.py` (click to expand):

```python my_mcp_server.py [expandable]
from fastmcp import FastMCP

# 1. Create the server
mcp = FastMCP(name="My First MCP Server")

# 2. Add a tool
@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b

# 3. Add a static resource
@mcp.resource("resource://config")
def get_config() -> dict:
    """Provides the application's configuration."""
    return {"version": "1.0", "author": "MyTeam"}

# 4. Add a resource template for dynamic content
@mcp.resource("greetings://{name}")
def personalized_greeting(name: str) -> str:
    """Generates a personalized greeting for the given name."""
    return f"Hello, {name}! Welcome to the MCP server."

# 5. Make the server runnable
if __name__ == "__main__":
    mcp.run()
```

## Next Steps

You've successfully built an MCP server! From here, you can explore more advanced topics:

-   [**Tools in Depth**](/servers/tools): Learn about asynchronous tools, error handling, and custom return types.
-   [**Resources & Templates**](/servers/resources): Discover different resource types, including files and HTTP endpoints.
-   [**Prompts**](/servers/prompts): Create reusable prompt templates for your LLM.
-   [**Running Your Server**](/deployment/running-server): Deploy your server with different transports like HTTP.

================
File: docs/tutorials/mcp.mdx
================
---
title: "What is the Model Context Protocol (MCP)?"
sidebarTitle: "What is MCP?"
description: "An introduction to the core concepts of the Model Context Protocol (MCP), explaining what it is, why it's useful, and how it works."
icon: "diagram-project"
---

The Model Context Protocol (MCP) is an open standard designed to solve a fundamental problem in AI development: how can Large Language Models (LLMs) reliably and securely interact with external tools, data, and services?

It's the **bridge between the probabilistic, non-deterministic world of AI and the deterministic, reliable world of your code and data.**

While you could build a custom REST API for your LLM, MCP provides a specialized, standardized "port" for AI-native communication. Think of it as **USB-C for AI**: a single, well-defined interface for connecting any compliant LLM to any compliant tool or data source.

This guide provides a high-level overview of the protocol itself. We'll use **FastMCP**, the leading Python framework for MCP, to illustrate the concepts with simple code examples.

## Why Do We Need a Protocol?

With countless APIs already in existence, the most common question is: "Why do we need another one?"

The answer lies in **standardization**. The AI ecosystem is fragmented. Every model provider has its own way of defining and calling tools. MCP's goal is to create a common language that offers several key advantages:

1.  **Interoperability:** Build one MCP server, and it can be used by any MCP-compliant client (Claude, Gemini, OpenAI, custom agents, etc.) without custom integration code. This is the protocol's most important promise.
2.  **Discoverability:** Clients can dynamically ask a server what it's capable of at runtime. They receive a structured, machine-readable "menu" of tools and resources.
3.  **Security & Safety:** MCP provides a clear, sandboxed boundary. An LLM can't execute arbitrary code on your server; it can only *request* to run the specific, typed, and validated functions you explicitly expose.
4.  **Composability:** You can build small, specialized MCP servers and combine them to create powerful, complex applications.

## Core MCP Components 

An MCP server exposes its capabilities through three primary components: Tools, Resources, and Prompts.

### Tools: Executable Actions

Tools are functions that the LLM can ask the server to execute. They are the action-oriented part of MCP.

In the spirit of a REST API, you can think of **Tools as being like `POST` requests.** They are used to *perform an action*, *change state*, or *trigger a side effect*, like sending an email, adding a user to a database, or making a calculation.

With FastMCP, creating a tool is as simple as decorating a Python function.

```python
from fastmcp import FastMCP

mcp = FastMCP()

# This function is now an MCP tool named "get_weather"
@mcp.tool
def get_weather(city: str) -> dict:
    """Gets the current weather for a specific city."""
    # In a real app, this would call a weather API
    return {"city": city, "temperature": "72F", "forecast": "Sunny"}
```

[**Learn more about Tools →**](/servers/tools)

### Resources: Read-Only Data

Resources are data sources that the LLM can read. They are used to load information into the LLM's context, providing it with knowledge it doesn't have from its training data.

Following the REST API analogy, **Resources are like `GET` requests.** Their purpose is to *retrieve information* idempotently, ideally without causing side effects. A resource can be anything from a static text file to a dynamic piece of data from a database. Each resource is identified by a unique URI.

```python
from fastmcp import FastMCP

mcp = FastMCP()

# This function provides a resource at the URI "system://status"
@mcp.resource("system://status")
def get_system_status() -> dict:
    """Returns the current operational status of the service."""
    return {"status": "all systems normal"}
```

#### Resource Templates

You can also create **Resource Templates** for dynamic data. A client could request `users://42/profile` to get the profile for a specific user.

```python
from fastmcp import FastMCP

mcp = FastMCP()

# This template provides user data for any given user ID
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> dict:
    """Returns the profile for a specific user."""
    # Fetch user from a database...
    return {"id": user_id, "name": "Zaphod Beeblebrox"}
```

[**Learn more about Resources & Templates →**](/servers/resources)

### Prompts: Reusable Instructions

Prompts are reusable, parameterized message templates. They provide a way to define consistent, structured instructions that a client can request to guide the LLM's behavior for a specific task.

```python
from fastmcp import FastMCP

mcp = FastMCP()

@mcp.prompt
def summarize_text(text_to_summarize: str) -> str:
    """Creates a prompt asking the LLM to summarize a piece of text."""
    return f"""
        Please provide a concise, one-paragraph summary of the following text:
        
        {text_to_summarize}
        """
```

[**Learn more about Prompts →**](/servers/prompts)

## Advanced Capabilities

Beyond the core components, MCP also supports more advanced interaction patterns, such as a server requesting that the *client's* LLM generate a completion (known as **sampling**), or a server sending asynchronous **notifications** to a client. These features enable more complex, bidirectional workflows and are fully supported by FastMCP.

## Next Steps

Now that you understand the core concepts of the Model Context Protocol, you're ready to start building. The best place to begin is our step-by-step tutorial.

[**Tutorial: How to Create an MCP Server in Python →**](/tutorials/create-mcp-server)

================
File: docs/tutorials/rest-api.mdx
================
---
title: "How to Connect an LLM to a REST API"
sidebarTitle: "Connect LLMs to REST APIs"
description: "A step-by-step guide to making any REST API with an OpenAPI spec available to LLMs using FastMCP."
icon: "plug"
---

You've built a powerful REST API, and now you want your LLM to be able to use it. Manually writing a wrapper function for every single endpoint is tedious, error-prone, and hard to maintain.

This is where **FastMCP** shines. If your API has an OpenAPI (or Swagger) specification, FastMCP can automatically convert your entire API into a fully-featured MCP server, making every endpoint available as a secure, typed tool for your AI model.

This guide will walk you through converting a public REST API into an MCP server in just a few lines of code.

<Tip>
Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out.
</Tip>

### Prerequisites

Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation).

```bash
pip install fastmcp
```

## Step 1: Choose a Target API

For this tutorial, we'll use the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/), a free, fake online REST API for testing and prototyping. It's perfect because it's simple and has a public OpenAPI specification.

-   **API Base URL:** `https://jsonplaceholder.typicode.com`
-   **OpenAPI Spec URL:** We'll use a community-provided spec for it.

## Step 2: Create the MCP Server

Now for the magic. We'll use `FastMCP.from_openapi`. This method takes an `httpx.AsyncClient` configured for your API and its OpenAPI specification, and automatically converts **every endpoint** into a callable MCP `Tool`.

<Tip>
Learn more about working with OpenAPI specs in the [OpenAPI integration docs](/servers/openapi).
</Tip>

<Note>
For this tutorial, we'll use a simplified OpenAPI spec directly in the code. In a real project, you would typically load the spec from a URL or local file.
</Note>

Create a file named `api_server.py`:

```python api_server.py {31-35}
import httpx
from fastmcp import FastMCP

# Create an HTTP client for the target API
client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com")

# Define a simplified OpenAPI spec for JSONPlaceholder
openapi_spec = {
    "openapi": "3.0.0",
    "info": {"title": "JSONPlaceholder API", "version": "1.0"},
    "paths": {
        "/users": {
            "get": {
                "summary": "Get all users",
                "operationId": "get_users",
                "responses": {"200": {"description": "A list of users."}}
            }
        },
        "/users/{id}": {
            "get": {
                "summary": "Get a user by ID",
                "operationId": "get_user_by_id",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "responses": {"200": {"description": "A single user."}}
            }
        }
    }
}

# Create the MCP server from the OpenAPI spec
mcp = FastMCP.from_openapi(
    openapi_spec=openapi_spec,
    client=client,
    name="JSONPlaceholder MCP Server"
)

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)
```

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.

## Step 3: Test the Generated Server

Let's verify that our new MCP server works. We can use the `fastmcp.Client` to connect to it and inspect its tools.

<Tip>
Learn more about the FastMCP client in the [client docs](/clients/client).
</Tip>

Create a separate file, `api_client.py`:

```python api_client.py {2, 6, 9, 16}
import asyncio
from fastmcp import Client

async def main():
    # Connect to the MCP server we just created
    async with Client("http://127.0.0.1:8000/mcp/") as client:
        
        # List the tools that were automatically generated
        tools = await client.list_tools()
        print("Generated Tools:")
        for tool in tools:
            print(f"- {tool.name}")
            
        # Call one of the generated tools
        print("\n\nCalling tool 'get_user_by_id'...")
        user = await client.call_tool("get_user_by_id", {"id": 1})
        print(f"Result:\n{user[0].text}")

if __name__ == "__main__":
    asyncio.run(main())
```

First, run your server:
```bash
python api_server.py
```

Then, in another terminal, run the client:
```bash
python api_client.py
```

You should see a list of generated tools (`get_users`, `get_user_by_id`) and the result of calling the `get_user_by_id` tool, which fetches data from the live JSONPlaceholder API.

![](/assets/images/tutorial-rest-api-result.png)


## Step 4: Customizing Route Maps

By default, FastMCP converts every API endpoint into an MCP `Tool`. This ensures maximum compatibility with contemporary LLM clients, many of which **only support the `tools` part of the MCP specification.**

However, for clients that support the full MCP spec, representing `GET` requests as `Resources` can be more semantically correct and efficient.

FastMCP allows users to customize this behavior using the concept of "route maps". A `RouteMap` is a mapping of an API route to an MCP type. FastMCP checks each API route against your custom maps in order. If a route matches a map, it's converted to the specified `mcp_type`. Any route that doesn't match your custom maps will fall back to the default behavior (becoming a `Tool`).

<Tip>
Learn more about route maps in the [OpenAPI integration docs](/servers/openapi#route-mapping).
</Tip>

Here’s how you can add custom route maps to turn `GET` requests into `Resources` and `ResourceTemplates` (if they have path parameters):

```python api_server_with_resources.py {3, 37-42}
import httpx
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType


# Create an HTTP client for the target API
client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com")

# Define a simplified OpenAPI spec for JSONPlaceholder
openapi_spec = {
    "openapi": "3.0.0",
    "info": {"title": "JSONPlaceholder API", "version": "1.0"},
    "paths": {
        "/users": {
            "get": {
                "summary": "Get all users",
                "operationId": "get_users",
                "responses": {"200": {"description": "A list of users."}}
            }
        },
        "/users/{id}": {
            "get": {
                "summary": "Get a user by ID",
                "operationId": "get_user_by_id",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "responses": {"200": {"description": "A single user."}}
            }
        }
    }
}

# Create the MCP server with custom route mapping
mcp = FastMCP.from_openapi(
    openapi_spec=openapi_spec,
    client=client,
    name="JSONPlaceholder MCP Server",
    route_maps=[
        # Map GET requests with path parameters (e.g., /users/{id}) to ResourceTemplate
        RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE),
        # Map all other GET requests to Resource
        RouteMap(methods=["GET"], mcp_type=MCPType.RESOURCE),
    ]
)

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)
```
With this configuration:
- `GET /users/{id}` becomes a `ResourceTemplate`.
- `GET /users` becomes a `Resource`.
- Any `POST`, `PUT`, etc. endpoints would still become `Tools` by default.

================
File: docs/changelog.mdx
================
---
icon: "list-check"
---

<Update label="v2.9.0" description="2024-06-23">

## [v2.9.0: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.9.0)

FastMCP 2.9 introduces two important features that push beyond the basic MCP protocol: MCP Middleware and server-side type conversion.

### MCP Middleware
MCP middleware lets you intercept and modify requests and responses at the protocol level, giving you powerful capabilities for logging, authentication, validation, and more. This is particularly useful for building production-ready MCP servers that need sophisticated request handling.

### Server-side Type Conversion
This release also introduces server-side type conversion for prompt arguments, ensuring that data is properly formatted before being passed to your functions. This reduces the burden on individual tools and prompts to handle type validation and conversion.

## What's Changed
### New Features 🎉
* Add File utility for binary data by [@gorocode](https://github.com/gorocode) in [#843](https://github.com/jlowin/fastmcp/pull/843)
* Consolidate prefix logic into FastMCP methods by [@jlowin](https://github.com/jlowin) in [#861](https://github.com/jlowin/fastmcp/pull/861)
* Add MCP Middleware by [@jlowin](https://github.com/jlowin) in [#870](https://github.com/jlowin/fastmcp/pull/870)
* Implement server-side type conversion for prompt arguments by [@jlowin](https://github.com/jlowin) in [#908](https://github.com/jlowin/fastmcp/pull/908)
### Enhancements 🔧
* Fix tool description indentation issue by [@zfflxx](https://github.com/zfflxx) in [#845](https://github.com/jlowin/fastmcp/pull/845)
* Add version parameter to FastMCP constructor by [@mkyutani](https://github.com/mkyutani) in [#842](https://github.com/jlowin/fastmcp/pull/842)
* Update version to not be positional by [@jlowin](https://github.com/jlowin) in [#848](https://github.com/jlowin/fastmcp/pull/848)
* Add key to component by [@jlowin](https://github.com/jlowin) in [#869](https://github.com/jlowin/fastmcp/pull/869)
* Add session_id property to Context for data sharing by [@jlowin](https://github.com/jlowin) in [#881](https://github.com/jlowin/fastmcp/pull/881)
* Fix CORS documentation example by [@jlowin](https://github.com/jlowin) in [#895](https://github.com/jlowin/fastmcp/pull/895)
### Fixes 🐞
* "report_progress missing passing related_request_id causes notifications not working" by [@alexsee](https://github.com/alexsee) in [#838](https://github.com/jlowin/fastmcp/pull/838)
* Fix JWT issuer validation to support string values per RFC 7519 by [@jlowin](https://github.com/jlowin) in [#892](https://github.com/jlowin/fastmcp/pull/892)
* Fix BearerAuthProvider audience type annotations by [@jlowin](https://github.com/jlowin) in [#894](https://github.com/jlowin/fastmcp/pull/894)
### Docs 📚
* Add CLAUDE.md development guidelines by [@jlowin](https://github.com/jlowin) in [#880](https://github.com/jlowin/fastmcp/pull/880)
* Update context docs for session_id property by [@jlowin](https://github.com/jlowin) in [#882](https://github.com/jlowin/fastmcp/pull/882)
* Add API reference by [@zzstoatzz](https://github.com/zzstoatzz) in [#893](https://github.com/jlowin/fastmcp/pull/893)
* Fix API ref rendering by [@zzstoatzz](https://github.com/zzstoatzz) in [#900](https://github.com/jlowin/fastmcp/pull/900)
* Simplify docs nav by [@jlowin](https://github.com/jlowin) in [#902](https://github.com/jlowin/fastmcp/pull/902)
* Add fastmcp inspect command by [@jlowin](https://github.com/jlowin) in [#904](https://github.com/jlowin/fastmcp/pull/904)
* Update client docs by [@jlowin](https://github.com/jlowin) in [#912](https://github.com/jlowin/fastmcp/pull/912)
* Update docs nav by [@jlowin](https://github.com/jlowin) in [#913](https://github.com/jlowin/fastmcp/pull/913)
* Update integration documentation for Claude Desktop, ChatGPT, and Claude Code by [@jlowin](https://github.com/jlowin) in [#915](https://github.com/jlowin/fastmcp/pull/915)
* Add http as an alias for streamable http by [@jlowin](https://github.com/jlowin) in [#917](https://github.com/jlowin/fastmcp/pull/917)
* Clean up parameter documentation by [@jlowin](https://github.com/jlowin) in [#918](https://github.com/jlowin/fastmcp/pull/918)
* Add middleware examples for timing, logging, rate limiting, and error handling by [@jlowin](https://github.com/jlowin) in [#919](https://github.com/jlowin/fastmcp/pull/919)
* ControlFlow → FastMCP rename by [@jlowin](https://github.com/jlowin) in [#922](https://github.com/jlowin/fastmcp/pull/922)
### Examples & Contrib 💡
* Add contrib.mcp_mixin support for annotations by [@rsp2k](https://github.com/rsp2k) in [#860](https://github.com/jlowin/fastmcp/pull/860)
* Add ATProto (Bluesky) MCP Server Example by [@zzstoatzz](https://github.com/zzstoatzz) in [#916](https://github.com/jlowin/fastmcp/pull/916)
* Fix path in atproto example pyproject by [@zzstoatzz](https://github.com/zzstoatzz) in [#920](https://github.com/jlowin/fastmcp/pull/920)
* Remove uv source in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#921](https://github.com/jlowin/fastmcp/pull/921)

## New Contributors
* [@alexsee](https://github.com/alexsee) made their first contribution in [#838](https://github.com/jlowin/fastmcp/pull/838)
* [@zfflxx](https://github.com/zfflxx) made their first contribution in [#845](https://github.com/jlowin/fastmcp/pull/845)
* [@mkyutani](https://github.com/mkyutani) made their first contribution in [#842](https://github.com/jlowin/fastmcp/pull/842)
* [@gorocode](https://github.com/gorocode) made their first contribution in [#843](https://github.com/jlowin/fastmcp/pull/843)
* [@rsp2k](https://github.com/rsp2k) made their first contribution in [#860](https://github.com/jlowin/fastmcp/pull/860)
* [@owtaylor](https://github.com/owtaylor) made their first contribution in [#897](https://github.com/jlowin/fastmcp/pull/897)
* [@Jason-CKY](https://github.com/Jason-CKY) made their first contribution in [#906](https://github.com/jlowin/fastmcp/pull/906)

**Full Changelog**: [v2.8.1...v2.9.0](https://github.com/jlowin/fastmcp/compare/v2.8.1...v2.9.0)

</Update>

<Update label="v2.8.1" description="2024-06-15">

## [v2.8.1: Sound Judgement](https://github.com/jlowin/fastmcp/releases/tag/v2.8.1)

2.8.1 introduces audio support, as well as minor fixes and updates for deprecated features.

### Audio Support
This release adds support for audio content in MCP tools and resources, expanding FastMCP's multimedia capabilities beyond text and images.

## What's Changed
### New Features 🎉
* Add audio support by [@jlowin](https://github.com/jlowin) in [#833](https://github.com/jlowin/fastmcp/pull/833)
### Enhancements 🔧
* Add flag for disabling deprecation warnings by [@jlowin](https://github.com/jlowin) in [#802](https://github.com/jlowin/fastmcp/pull/802)
* Add examples to Tool Arg Param transformation by [@strawgate](https://github.com/strawgate) in [#806](https://github.com/jlowin/fastmcp/pull/806)
### Fixes 🐞
* Restore .settings access as deprecated by [@jlowin](https://github.com/jlowin) in [#800](https://github.com/jlowin/fastmcp/pull/800)
* Ensure handling of false http kwargs correctly; removed unused kwarg by [@jlowin](https://github.com/jlowin) in [#804](https://github.com/jlowin/fastmcp/pull/804)
* Bump mcp 1.9.4 by [@jlowin](https://github.com/jlowin) in [#835](https://github.com/jlowin/fastmcp/pull/835)
### Docs 📚
* Update changelog for 2.8.0 by [@jlowin](https://github.com/jlowin) in [#794](https://github.com/jlowin/fastmcp/pull/794)
* Update welcome docs by [@jlowin](https://github.com/jlowin) in [#808](https://github.com/jlowin/fastmcp/pull/808)
* Update headers in docs by [@jlowin](https://github.com/jlowin) in [#809](https://github.com/jlowin/fastmcp/pull/809)
* Add MCP group to tutorials by [@jlowin](https://github.com/jlowin) in [#810](https://github.com/jlowin/fastmcp/pull/810)
* Add Community section to documentation by [@zzstoatzz](https://github.com/zzstoatzz) in [#819](https://github.com/jlowin/fastmcp/pull/819)
* Add 2.8 update by [@jlowin](https://github.com/jlowin) in [#821](https://github.com/jlowin/fastmcp/pull/821)
* Embed YouTube videos in community showcase by [@zzstoatzz](https://github.com/zzstoatzz) in [#820](https://github.com/jlowin/fastmcp/pull/820)
### Other Changes 🦾
* Ensure http args are passed through by [@jlowin](https://github.com/jlowin) in [#803](https://github.com/jlowin/fastmcp/pull/803)
* Fix install link in readme by [@jlowin](https://github.com/jlowin) in [#836](https://github.com/jlowin/fastmcp/pull/836)

**Full Changelog**: [v2.8.0...v2.8.1](https://github.com/jlowin/fastmcp/compare/v2.8.0...v2.8.1)

</Update>

<Update label="v2.8.0" description="2024-06-10">

## [v2.8.0: Transform and Roll Out](https://github.com/jlowin/fastmcp/releases/tag/v2.8.0)

FastMCP 2.8.0 introduces powerful new ways to customize and control your MCP servers! 

### Tool Transformation

The highlight of this release is first-class [**Tool Transformation**](/patterns/tool-transformation), a new feature that lets you create enhanced variations of existing tools. You can now easily rename arguments, hide parameters, modify descriptions, and even wrap tools with custom validation or post-processing logic—all without rewriting the original code. This makes it easier than ever to adapt generic tools for specific LLM use cases or to simplify complex APIs. Huge thanks to [@strawgate](https://github.com/strawgate) for partnering on this, starting with [#591](https://github.com/jlowin/fastmcp/discussions/591) and [#599](https://github.com/jlowin/fastmcp/pull/599) and continuing offline.

### Component Control
This release also gives you more granular control over which components are exposed to clients. With new [**tag-based filtering**](/servers/fastmcp#tag-based-filtering), you can selectively enable or disable tools, resources, and prompts based on tags, perfect for managing different environments or user permissions. Complementing this, every component now supports being [programmatically enabled or disabled](/servers/tools#disabling-tools), offering dynamic control over your server's capabilities.

### Tools-by-Default
Finally, to improve compatibility with a wider range of LLM clients, this release changes the default behavior for OpenAPI integration: all API endpoints are now converted to `Tools` by default. This is a **breaking change** but pragmatically necessitated by the fact that the majority of MCP clients available today are, sadly, only compatible with MCP tools. Therefore, this change significantly simplifies the out-of-the-box experience and ensures your entire API is immediately accessible to any tool-using agent.

## What's Changed
### New Features 🎉
* First-class tool transformation by [@jlowin](https://github.com/jlowin) in [#745](https://github.com/jlowin/fastmcp/pull/745)
* Support enable/disable for all FastMCP components (tools, prompts, resources, templates) by [@jlowin](https://github.com/jlowin) in [#781](https://github.com/jlowin/fastmcp/pull/781)
* Add support for tag-based component filtering by [@jlowin](https://github.com/jlowin) in [#748](https://github.com/jlowin/fastmcp/pull/748)
* Allow tag assignments for OpenAPI by [@jlowin](https://github.com/jlowin) in [#791](https://github.com/jlowin/fastmcp/pull/791)
### Enhancements 🔧
* Create common base class for components by [@jlowin](https://github.com/jlowin) in [#776](https://github.com/jlowin/fastmcp/pull/776)
* Move components to own file; add resource by [@jlowin](https://github.com/jlowin) in [#777](https://github.com/jlowin/fastmcp/pull/777)
* Update FastMCP component with __eq__ and __repr__ by [@jlowin](https://github.com/jlowin) in [#779](https://github.com/jlowin/fastmcp/pull/779)
* Remove open-ended and server-specific settings by [@jlowin](https://github.com/jlowin) in [#750](https://github.com/jlowin/fastmcp/pull/750)
### Fixes 🐞
* Ensure client is only initialized once by [@jlowin](https://github.com/jlowin) in [#758](https://github.com/jlowin/fastmcp/pull/758)
* Fix field validator for resource by [@jlowin](https://github.com/jlowin) in [#778](https://github.com/jlowin/fastmcp/pull/778)
* Ensure proxies can overwrite remote tools without falling back to the remote by [@jlowin](https://github.com/jlowin) in [#782](https://github.com/jlowin/fastmcp/pull/782)
### Breaking Changes 🛫
* Treat all openapi routes as tools by [@jlowin](https://github.com/jlowin) in [#788](https://github.com/jlowin/fastmcp/pull/788)
* Fix issue with global OpenAPI tags by [@jlowin](https://github.com/jlowin) in [#792](https://github.com/jlowin/fastmcp/pull/792)
### Docs 📚
* Minor docs updates by [@jlowin](https://github.com/jlowin) in [#755](https://github.com/jlowin/fastmcp/pull/755)
* Add 2.7 update by [@jlowin](https://github.com/jlowin) in [#756](https://github.com/jlowin/fastmcp/pull/756)
* Reduce 2.7 image size by [@jlowin](https://github.com/jlowin) in [#757](https://github.com/jlowin/fastmcp/pull/757)
* Update updates.mdx by [@jlowin](https://github.com/jlowin) in [#765](https://github.com/jlowin/fastmcp/pull/765)
* Hide docs sidebar scrollbar by default by [@jlowin](https://github.com/jlowin) in [#766](https://github.com/jlowin/fastmcp/pull/766)
* Add "stop vibe testing" to tutorials by [@jlowin](https://github.com/jlowin) in [#767](https://github.com/jlowin/fastmcp/pull/767)
* Add docs links by [@jlowin](https://github.com/jlowin) in [#768](https://github.com/jlowin/fastmcp/pull/768)
* Fix: updated variable name under Gemini remote client by [@yrangana](https://github.com/yrangana) in [#769](https://github.com/jlowin/fastmcp/pull/769)
* Revert "Hide docs sidebar scrollbar by default" by [@jlowin](https://github.com/jlowin) in [#770](https://github.com/jlowin/fastmcp/pull/770)
* Add updates by [@jlowin](https://github.com/jlowin) in [#773](https://github.com/jlowin/fastmcp/pull/773)
* Add tutorials by [@jlowin](https://github.com/jlowin) in [#783](https://github.com/jlowin/fastmcp/pull/783)
* Update LLM-friendly docs by [@jlowin](https://github.com/jlowin) in [#784](https://github.com/jlowin/fastmcp/pull/784)
* Update oauth.mdx by [@JeremyCraigMartinez](https://github.com/JeremyCraigMartinez) in [#787](https://github.com/jlowin/fastmcp/pull/787)
* Add changelog by [@jlowin](https://github.com/jlowin) in [#789](https://github.com/jlowin/fastmcp/pull/789)
* Add tutorials by [@jlowin](https://github.com/jlowin) in [#790](https://github.com/jlowin/fastmcp/pull/790)
* Add docs for tag-based filtering by [@jlowin](https://github.com/jlowin) in [#793](https://github.com/jlowin/fastmcp/pull/793)
### Other Changes 🦾
* Create dependabot.yml by [@jlowin](https://github.com/jlowin) in [#759](https://github.com/jlowin/fastmcp/pull/759)
* Bump astral-sh/setup-uv from 3 to 6 by [@dependabot](https://github.com/dependabot) in [#760](https://github.com/jlowin/fastmcp/pull/760)
* Add dependencies section to release by [@jlowin](https://github.com/jlowin) in [#761](https://github.com/jlowin/fastmcp/pull/761)
* Remove extra imports for MCPConfig by [@Maanas-Verma](https://github.com/Maanas-Verma) in [#763](https://github.com/jlowin/fastmcp/pull/763)
* Split out enhancements in release notes by [@jlowin](https://github.com/jlowin) in [#764](https://github.com/jlowin/fastmcp/pull/764)

## New Contributors
* [@dependabot](https://github.com/dependabot) made their first contribution in [#760](https://github.com/jlowin/fastmcp/pull/760)
* [@Maanas-Verma](https://github.com/Maanas-Verma) made their first contribution in [#763](https://github.com/jlowin/fastmcp/pull/763)
* [@JeremyCraigMartinez](https://github.com/JeremyCraigMartinez) made their first contribution in [#787](https://github.com/jlowin/fastmcp/pull/787)

**Full Changelog**: [v2.7.1...v2.8.0](https://github.com/jlowin/fastmcp/compare/v2.7.1...v2.8.0)

</Update>

<Update label="v2.7.1" description="2024-06-08">

## [v2.7.1: The Bearer Necessities](https://github.com/jlowin/fastmcp/releases/tag/v2.7.1)

This release primarily contains a fix for parsing string tokens that are provided to FastMCP clients.

### New Features 🎉

* Respect cache setting, set default to 1 second by [@jlowin](https://github.com/jlowin) in [#747](https://github.com/jlowin/fastmcp/pull/747)

### Fixes 🐞

* Ensure event store is properly typed by [@jlowin](https://github.com/jlowin) in [#753](https://github.com/jlowin/fastmcp/pull/753)
* Fix passing token string to client auth & add auth to MCPConfig clients by [@jlowin](https://github.com/jlowin) in [#754](https://github.com/jlowin/fastmcp/pull/754)

### Docs 📚

* Docs : fix client to mcp\_client in Gemini example by [@yrangana](https://github.com/yrangana) in [#734](https://github.com/jlowin/fastmcp/pull/734)
* update add tool docstring by [@strawgate](https://github.com/strawgate) in [#739](https://github.com/jlowin/fastmcp/pull/739)
* Fix contrib link by [@richardkmichael](https://github.com/richardkmichael) in [#749](https://github.com/jlowin/fastmcp/pull/749)

### Other Changes 🦾

* Switch Pydantic defaults to kwargs by [@strawgate](https://github.com/strawgate) in [#731](https://github.com/jlowin/fastmcp/pull/731)
* Fix Typo in CLI module by [@wfclark5](https://github.com/wfclark5) in [#737](https://github.com/jlowin/fastmcp/pull/737)
* chore: fix prompt docstring by [@danb27](https://github.com/danb27) in [#752](https://github.com/jlowin/fastmcp/pull/752)
* Add accept to excluded headers by [@jlowin](https://github.com/jlowin) in [#751](https://github.com/jlowin/fastmcp/pull/751)

### New Contributors

* [@wfclark5](https://github.com/wfclark5) made their first contribution in [#737](https://github.com/jlowin/fastmcp/pull/737)
* [@richardkmichael](https://github.com/richardkmichael) made their first contribution in [#749](https://github.com/jlowin/fastmcp/pull/749)
* [@danb27](https://github.com/danb27) made their first contribution in [#752](https://github.com/jlowin/fastmcp/pull/752)

**Full Changelog**: [v2.7.0...v2.7.1](https://github.com/jlowin/fastmcp/compare/v2.7.0...v2.7.1)
</Update>

<Update label="v2.7.0" description="2024-06-05">

## [v2.7.0: Pare Programming](https://github.com/jlowin/fastmcp/releases/tag/v2.7.0)

This is primarily a housekeeping release to remove or deprecate cruft that's accumulated since v1. Primarily, this release refactors FastMCP's internals in preparation for features planned in the next few major releases. However please note that as a result, this release has some minor breaking changes (which is why it's 2.7, not 2.6.2, in accordance with repo guidelines) though not to the core user-facing APIs.

### Breaking Changes 🛫

* decorators return the objects they create, not the decorated function
* websockets is an optional dependency
* methods on the server for automatically converting functions into tools/resources/prompts have been deprecated in favor of using the decorators directly

### New Features 🎉

* allow passing flags to servers by [@zzstoatzz](https://github.com/zzstoatzz) in [#690](https://github.com/jlowin/fastmcp/pull/690)
* replace $ref pointing to `#/components/schemas/` with `#/$defs/` by [@phateffect](https://github.com/phateffect) in [#697](https://github.com/jlowin/fastmcp/pull/697)
* Split Tool into Tool and FunctionTool by [@jlowin](https://github.com/jlowin) in [#700](https://github.com/jlowin/fastmcp/pull/700)
* Use strict basemodel for Prompt; relax from\_function deprecation by [@jlowin](https://github.com/jlowin) in [#701](https://github.com/jlowin/fastmcp/pull/701)
* Formalize resource/functionresource replationship by [@jlowin](https://github.com/jlowin) in [#702](https://github.com/jlowin/fastmcp/pull/702)
* Formalize template/functiontemplate split by [@jlowin](https://github.com/jlowin) in [#703](https://github.com/jlowin/fastmcp/pull/703)
* Support flexible @tool decorator call patterns by [@jlowin](https://github.com/jlowin) in [#706](https://github.com/jlowin/fastmcp/pull/706)
* Ensure deprecation warnings have stacklevel=2 by [@jlowin](https://github.com/jlowin) in [#710](https://github.com/jlowin/fastmcp/pull/710)
* Allow naked prompt decorator by [@jlowin](https://github.com/jlowin) in [#711](https://github.com/jlowin/fastmcp/pull/711)

### Fixes 🐞

* Updates / Fixes for Tool Content Conversion by [@strawgate](https://github.com/strawgate) in [#642](https://github.com/jlowin/fastmcp/pull/642)
* Fix pr labeler permissions by [@jlowin](https://github.com/jlowin) in [#708](https://github.com/jlowin/fastmcp/pull/708)
* remove -n auto by [@jlowin](https://github.com/jlowin) in [#709](https://github.com/jlowin/fastmcp/pull/709)
* Fix links in README.md by [@alainivars](https://github.com/alainivars) in [#723](https://github.com/jlowin/fastmcp/pull/723)

Happily, this release DOES permit the use of "naked" decorators to align with Pythonic practice:

```python
@mcp.tool
def my_tool():
    ...
```

**Full Changelog**: [v2.6.2...v2.7.0](https://github.com/jlowin/fastmcp/compare/v2.6.2...v2.7.0)
</Update>

<Update label="v2.6.1" description="2024-06-03">

## [v2.6.1: Blast Auth (second ignition)](https://github.com/jlowin/fastmcp/releases/tag/v2.6.1)

This is a patch release to restore py.typed in #686.

### Docs 📚

* Update readme by [@jlowin](https://github.com/jlowin) in [#679](https://github.com/jlowin/fastmcp/pull/679)
* Add gemini tutorial by [@jlowin](https://github.com/jlowin) in [#680](https://github.com/jlowin/fastmcp/pull/680)
* Fix : fix path error to CLI Documentation by [@yrangana](https://github.com/yrangana) in [#684](https://github.com/jlowin/fastmcp/pull/684)
* Update auth docs by [@jlowin](https://github.com/jlowin) in [#687](https://github.com/jlowin/fastmcp/pull/687)

### Other Changes 🦾

* Remove deprecation notice by [@jlowin](https://github.com/jlowin) in [#677](https://github.com/jlowin/fastmcp/pull/677)
* Delete server.py by [@jlowin](https://github.com/jlowin) in [#681](https://github.com/jlowin/fastmcp/pull/681)
* Restore py.typed by [@jlowin](https://github.com/jlowin) in [#686](https://github.com/jlowin/fastmcp/pull/686)

### New Contributors

* [@yrangana](https://github.com/yrangana) made their first contribution in [#684](https://github.com/jlowin/fastmcp/pull/684)

**Full Changelog**: [v2.6.0...v2.6.1](https://github.com/jlowin/fastmcp/compare/v2.6.0...v2.6.1)
</Update>

<Update label="v2.6.0" description="2024-06-02">

## [v2.6.0: Blast Auth](https://github.com/jlowin/fastmcp/releases/tag/v2.6.0)

### New Features 🎉

* Introduce MCP client oauth flow by [@jlowin](https://github.com/jlowin) in [#478](https://github.com/jlowin/fastmcp/pull/478)
* Support providing tools at init by [@jlowin](https://github.com/jlowin) in [#647](https://github.com/jlowin/fastmcp/pull/647)
* Simplify code for running servers in processes during tests by [@jlowin](https://github.com/jlowin) in [#649](https://github.com/jlowin/fastmcp/pull/649)
* Add basic bearer auth for server and client by [@jlowin](https://github.com/jlowin) in [#650](https://github.com/jlowin/fastmcp/pull/650)
* Support configuring bearer auth from env vars by [@jlowin](https://github.com/jlowin) in [#652](https://github.com/jlowin/fastmcp/pull/652)
* feat(tool): add support for excluding arguments from tool definition by [@deepak-stratforge](https://github.com/deepak-stratforge) in [#626](https://github.com/jlowin/fastmcp/pull/626)
* Add docs for server + client auth by [@jlowin](https://github.com/jlowin) in [#655](https://github.com/jlowin/fastmcp/pull/655)

### Fixes 🐞

* fix: Support concurrency in FastMcpProxy (and Client) by [@Sillocan](https://github.com/Sillocan) in [#635](https://github.com/jlowin/fastmcp/pull/635)
* Ensure Client.close() cleans up client context appropriately by [@jlowin](https://github.com/jlowin) in [#643](https://github.com/jlowin/fastmcp/pull/643)
* Update client.mdx: ClientError namespace by [@mjkaye](https://github.com/mjkaye) in [#657](https://github.com/jlowin/fastmcp/pull/657)

### Docs 📚

* Make FastMCPTransport support simulated Streamable HTTP Transport (didn't work) by [@jlowin](https://github.com/jlowin) in [#645](https://github.com/jlowin/fastmcp/pull/645)
* Document exclude\_args by [@jlowin](https://github.com/jlowin) in [#653](https://github.com/jlowin/fastmcp/pull/653)
* Update welcome by [@jlowin](https://github.com/jlowin) in [#673](https://github.com/jlowin/fastmcp/pull/673)
* Add Anthropic + Claude desktop integration guides by [@jlowin](https://github.com/jlowin) in [#674](https://github.com/jlowin/fastmcp/pull/674)
* Minor docs design updates by [@jlowin](https://github.com/jlowin) in [#676](https://github.com/jlowin/fastmcp/pull/676)

### Other Changes 🦾

* Update test typing by [@jlowin](https://github.com/jlowin) in [#646](https://github.com/jlowin/fastmcp/pull/646)
* Add OpenAI integration docs by [@jlowin](https://github.com/jlowin) in [#660](https://github.com/jlowin/fastmcp/pull/660)

### New Contributors

* [@Sillocan](https://github.com/Sillocan) made their first contribution in [#635](https://github.com/jlowin/fastmcp/pull/635)
* [@deepak-stratforge](https://github.com/deepak-stratforge) made their first contribution in [#626](https://github.com/jlowin/fastmcp/pull/626)
* [@mjkaye](https://github.com/mjkaye) made their first contribution in [#657](https://github.com/jlowin/fastmcp/pull/657)

**Full Changelog**: [v2.5.2...v2.6.0](https://github.com/jlowin/fastmcp/compare/v2.5.2...v2.6.0)
</Update>

<Update label="v2.5.2" description="2024-05-29">

## [v2.5.2: Stayin' Alive](https://github.com/jlowin/fastmcp/releases/tag/v2.5.2)

### New Features 🎉

* Add graceful error handling for unreachable mounted servers by [@davenpi](https://github.com/davenpi) in [#605](https://github.com/jlowin/fastmcp/pull/605)
* Improve type inference from client transport by [@jlowin](https://github.com/jlowin) in [#623](https://github.com/jlowin/fastmcp/pull/623)
* Add keep\_alive param to reuse subprocess by [@jlowin](https://github.com/jlowin) in [#624](https://github.com/jlowin/fastmcp/pull/624)

### Fixes 🐞

* Fix handling tools without descriptions by [@jlowin](https://github.com/jlowin) in [#610](https://github.com/jlowin/fastmcp/pull/610)
* Don't print env vars to console when format is wrong by [@jlowin](https://github.com/jlowin) in [#615](https://github.com/jlowin/fastmcp/pull/615)
* Ensure behavior-affecting headers are excluded when forwarding proxies/openapi by [@jlowin](https://github.com/jlowin) in [#620](https://github.com/jlowin/fastmcp/pull/620)

### Docs 📚

* Add notes about uv and claude desktop by [@jlowin](https://github.com/jlowin) in [#597](https://github.com/jlowin/fastmcp/pull/597)

### Other Changes 🦾

* add init\_timeout for mcp client by [@jfouret](https://github.com/jfouret) in [#607](https://github.com/jlowin/fastmcp/pull/607)
* Add init\_timeout for mcp client (incl settings) by [@jlowin](https://github.com/jlowin) in [#609](https://github.com/jlowin/fastmcp/pull/609)
* Support for uppercase letters at the log level by [@ksawaray](https://github.com/ksawaray) in [#625](https://github.com/jlowin/fastmcp/pull/625)

### New Contributors

* [@jfouret](https://github.com/jfouret) made their first contribution in [#607](https://github.com/jlowin/fastmcp/pull/607)
* [@ksawaray](https://github.com/ksawaray) made their first contribution in [#625](https://github.com/jlowin/fastmcp/pull/625)

**Full Changelog**: [v2.5.1...v2.5.2](https://github.com/jlowin/fastmcp/compare/v2.5.1...v2.5.2)
</Update>

<Update label="v2.5.1" description="2024-05-24">

## [v2.5.1: Route Awakening (Part 2)](https://github.com/jlowin/fastmcp/releases/tag/v2.5.1)

### Fixes 🐞

* Ensure content-length is always stripped from client headers by [@jlowin](https://github.com/jlowin) in [#589](https://github.com/jlowin/fastmcp/pull/589)

### Docs 📚

* Fix redundant section of docs by [@jlowin](https://github.com/jlowin) in [#583](https://github.com/jlowin/fastmcp/pull/583)

**Full Changelog**: [v2.5.0...v2.5.1](https://github.com/jlowin/fastmcp/compare/v2.5.0...v2.5.1)
</Update>

<Update label="v2.5.0" description="2024-05-24">

## [v2.5.0: Route Awakening](https://github.com/jlowin/fastmcp/releases/tag/v2.5.0)

This release introduces completely new tools for generating and customizing MCP servers from OpenAPI specs and FastAPI apps, including popular requests like mechanisms for determining what routes map to what MCP components; renaming routes; and customizing the generated MCP components.

### New Features 🎉

* Add FastMCP 1.0 server support for in-memory Client / Testing by [@jlowin](https://github.com/jlowin) in [#539](https://github.com/jlowin/fastmcp/pull/539)
* Minor addition: add transport to stdio server in mcpconfig, with default by [@jlowin](https://github.com/jlowin) in [#555](https://github.com/jlowin/fastmcp/pull/555)
* Raise an error if a Client is created with no servers in config by [@jlowin](https://github.com/jlowin) in [#554](https://github.com/jlowin/fastmcp/pull/554)
* Expose model preferences in `Context.sample` for flexible model selection. by [@davenpi](https://github.com/davenpi) in [#542](https://github.com/jlowin/fastmcp/pull/542)
* Ensure custom routes are respected by [@jlowin](https://github.com/jlowin) in [#558](https://github.com/jlowin/fastmcp/pull/558)
* Add client method to send cancellation notifications by [@davenpi](https://github.com/davenpi) in [#563](https://github.com/jlowin/fastmcp/pull/563)
* Enhance route map logic for include/exclude OpenAPI routes by [@jlowin](https://github.com/jlowin) in [#564](https://github.com/jlowin/fastmcp/pull/564)
* Add tag-based route maps by [@jlowin](https://github.com/jlowin) in [#565](https://github.com/jlowin/fastmcp/pull/565)
* Add advanced control of openAPI route creation by [@jlowin](https://github.com/jlowin) in [#566](https://github.com/jlowin/fastmcp/pull/566)
* Make error masking configurable by [@jlowin](https://github.com/jlowin) in [#550](https://github.com/jlowin/fastmcp/pull/550)
* Ensure client headers are passed through to remote servers by [@jlowin](https://github.com/jlowin) in [#575](https://github.com/jlowin/fastmcp/pull/575)
* Use lowercase name for headers when comparing by [@jlowin](https://github.com/jlowin) in [#576](https://github.com/jlowin/fastmcp/pull/576)
* Permit more flexible name generation for OpenAPI servers by [@jlowin](https://github.com/jlowin) in [#578](https://github.com/jlowin/fastmcp/pull/578)
* Ensure that tools/templates/prompts are compatible with callable objects by [@jlowin](https://github.com/jlowin) in [#579](https://github.com/jlowin/fastmcp/pull/579)

### Docs 📚

* Add version badge for prefix formats by [@jlowin](https://github.com/jlowin) in [#537](https://github.com/jlowin/fastmcp/pull/537)
* Add versioning note to docs by [@jlowin](https://github.com/jlowin) in [#551](https://github.com/jlowin/fastmcp/pull/551)
* Bump 2.3.6 references to 2.4.0 by [@jlowin](https://github.com/jlowin) in [#567](https://github.com/jlowin/fastmcp/pull/567)

**Full Changelog**: [v2.4.0...v2.5.0](https://github.com/jlowin/fastmcp/compare/v2.4.0...v2.5.0)
</Update>

<Update label="v2.4.0" description="2024-05-21">

## [v2.4.0: Config and Conquer](https://github.com/jlowin/fastmcp/releases/tag/v2.4.0)

**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route. See [Resource Prefix Formats](https://gofastmcp.com/servers/composition#resource-prefix-formats) for more.

### New Features 🎉

* Allow \* Methods and all routes as tools shortcuts by [@jlowin](https://github.com/jlowin) in [#520](https://github.com/jlowin/fastmcp/pull/520)
* Improved support for config dicts by [@jlowin](https://github.com/jlowin) in [#522](https://github.com/jlowin/fastmcp/pull/522)
* Support creating clients from MCP config dicts, including multi-server clients by [@jlowin](https://github.com/jlowin) in [#527](https://github.com/jlowin/fastmcp/pull/527)
* Make resource prefix format configurable by [@jlowin](https://github.com/jlowin) in [#534](https://github.com/jlowin/fastmcp/pull/534)

### Fixes 🐞

* Avoid hanging on initializing server session by [@jlowin](https://github.com/jlowin) in [#523](https://github.com/jlowin/fastmcp/pull/523)

### Breaking Changes 🛫

* Remove customizable separators; improve resource separator by [@jlowin](https://github.com/jlowin) in [#526](https://github.com/jlowin/fastmcp/pull/526)

### Docs 📚

* Improve client documentation by [@jlowin](https://github.com/jlowin) in [#517](https://github.com/jlowin/fastmcp/pull/517)

### Other Changes 🦾

* Ensure openapi path params are handled properly by [@jlowin](https://github.com/jlowin) in [#519](https://github.com/jlowin/fastmcp/pull/519)
* better error when missing lifespan by [@zzstoatzz](https://github.com/zzstoatzz) in [#521](https://github.com/jlowin/fastmcp/pull/521)

**Full Changelog**: [v2.3.5...v2.4.0](https://github.com/jlowin/fastmcp/compare/v2.3.5...v2.4.0)
</Update>

<Update label="v2.3.5" description="2024-05-20">

## [v2.3.5: Making Progress](https://github.com/jlowin/fastmcp/releases/tag/v2.3.5)

### New Features 🎉

* support messages in progress notifications by [@rickygenhealth](https://github.com/rickygenhealth) in [#471](https://github.com/jlowin/fastmcp/pull/471)
* feat: Add middleware option in server.run by [@Maxi91f](https://github.com/Maxi91f) in [#475](https://github.com/jlowin/fastmcp/pull/475)
* Add lifespan property to app by [@jlowin](https://github.com/jlowin) in [#483](https://github.com/jlowin/fastmcp/pull/483)
* Update `fastmcp run` to work with remote servers by [@jlowin](https://github.com/jlowin) in [#491](https://github.com/jlowin/fastmcp/pull/491)
* Add FastMCP.as\_proxy() by [@jlowin](https://github.com/jlowin) in [#490](https://github.com/jlowin/fastmcp/pull/490)
* Infer sse transport from urls containing /sse by [@jlowin](https://github.com/jlowin) in [#512](https://github.com/jlowin/fastmcp/pull/512)
* Add progress handler to client by [@jlowin](https://github.com/jlowin) in [#513](https://github.com/jlowin/fastmcp/pull/513)
* Store the initialize result on the client by [@jlowin](https://github.com/jlowin) in [#509](https://github.com/jlowin/fastmcp/pull/509)

### Fixes 🐞

* Remove patch and use upstream SSEServerTransport by [@jlowin](https://github.com/jlowin) in [#425](https://github.com/jlowin/fastmcp/pull/425)

### Docs 📚

* Update transport docs by [@jlowin](https://github.com/jlowin) in [#458](https://github.com/jlowin/fastmcp/pull/458)
* update proxy docs + example by [@zzstoatzz](https://github.com/zzstoatzz) in [#460](https://github.com/jlowin/fastmcp/pull/460)
* doc(asgi): Change custom route example to PlainTextResponse by [@mcw0933](https://github.com/mcw0933) in [#477](https://github.com/jlowin/fastmcp/pull/477)
* Store FastMCP instance on app.state.fastmcp\_server by [@jlowin](https://github.com/jlowin) in [#489](https://github.com/jlowin/fastmcp/pull/489)
* Improve AGENTS.md overview by [@jlowin](https://github.com/jlowin) in [#492](https://github.com/jlowin/fastmcp/pull/492)
* Update release numbers for anticipated version by [@jlowin](https://github.com/jlowin) in [#516](https://github.com/jlowin/fastmcp/pull/516)

### Other Changes 🦾

* run tests on all PRs by [@jlowin](https://github.com/jlowin) in [#468](https://github.com/jlowin/fastmcp/pull/468)
* add null check by [@zzstoatzz](https://github.com/zzstoatzz) in [#473](https://github.com/jlowin/fastmcp/pull/473)
* strict typing for `server.py` by [@zzstoatzz](https://github.com/zzstoatzz) in [#476](https://github.com/jlowin/fastmcp/pull/476)
* Doc(quickstart): Fix import statements by [@mai-nakagawa](https://github.com/mai-nakagawa) in [#479](https://github.com/jlowin/fastmcp/pull/479)
* Add labeler by [@jlowin](https://github.com/jlowin) in [#484](https://github.com/jlowin/fastmcp/pull/484)
* Fix flaky timeout test by increasing timeout (#474) by [@davenpi](https://github.com/davenpi) in [#486](https://github.com/jlowin/fastmcp/pull/486)
* Skipping `test_permission_error` if runner is root. by [@ZiadAmerr](https://github.com/ZiadAmerr) in [#502](https://github.com/jlowin/fastmcp/pull/502)
* allow passing full uvicorn config by [@zzstoatzz](https://github.com/zzstoatzz) in [#504](https://github.com/jlowin/fastmcp/pull/504)
* Skip timeout tests on windows by [@jlowin](https://github.com/jlowin) in [#514](https://github.com/jlowin/fastmcp/pull/514)

### New Contributors

* [@rickygenhealth](https://github.com/rickygenhealth) made their first contribution in [#471](https://github.com/jlowin/fastmcp/pull/471)
* [@Maxi91f](https://github.com/Maxi91f) made their first contribution in [#475](https://github.com/jlowin/fastmcp/pull/475)
* [@mcw0933](https://github.com/mcw0933) made their first contribution in [#477](https://github.com/jlowin/fastmcp/pull/477)
* [@mai-nakagawa](https://github.com/mai-nakagawa) made their first contribution in [#479](https://github.com/jlowin/fastmcp/pull/479)
* [@ZiadAmerr](https://github.com/ZiadAmerr) made their first contribution in [#502](https://github.com/jlowin/fastmcp/pull/502)

**Full Changelog**: [v2.3.4...v2.3.5](https://github.com/jlowin/fastmcp/compare/v2.3.4...v2.3.5)
</Update>

<Update label="v2.3.4" description="2024-05-15">

## [v2.3.4: Error Today, Gone Tomorrow](https://github.com/jlowin/fastmcp/releases/tag/v2.3.4)

### New Features 🎉

* logging stack trace for easier debugging by [@jbkoh](https://github.com/jbkoh) in [#413](https://github.com/jlowin/fastmcp/pull/413)
* add missing StreamableHttpTransport in client exports by [@yihuang](https://github.com/yihuang) in [#408](https://github.com/jlowin/fastmcp/pull/408)
* Improve error handling for tools and resources by [@jlowin](https://github.com/jlowin) in [#434](https://github.com/jlowin/fastmcp/pull/434)
* feat: add support for removing tools from server by [@davenpi](https://github.com/davenpi) in [#437](https://github.com/jlowin/fastmcp/pull/437)
* Prune titles from JSONSchemas by [@jlowin](https://github.com/jlowin) in [#449](https://github.com/jlowin/fastmcp/pull/449)
* Declare toolsChanged capability for stdio server. by [@davenpi](https://github.com/davenpi) in [#450](https://github.com/jlowin/fastmcp/pull/450)
* Improve handling of exceptiongroups when raised in clients by [@jlowin](https://github.com/jlowin) in [#452](https://github.com/jlowin/fastmcp/pull/452)
* Add timeout support to client by [@jlowin](https://github.com/jlowin) in [#455](https://github.com/jlowin/fastmcp/pull/455)

### Fixes 🐞

* Pin to mcp 1.8.1 to resolve callback deadlocks with SHTTP by [@jlowin](https://github.com/jlowin) in [#427](https://github.com/jlowin/fastmcp/pull/427)
* Add reprs for OpenAPI objects by [@jlowin](https://github.com/jlowin) in [#447](https://github.com/jlowin/fastmcp/pull/447)
* Ensure openapi defs for structured objects are loaded properly by [@jlowin](https://github.com/jlowin) in [#448](https://github.com/jlowin/fastmcp/pull/448)
* Ensure tests run against correct python version by [@jlowin](https://github.com/jlowin) in [#454](https://github.com/jlowin/fastmcp/pull/454)
* Ensure result is only returned if a new key was found by [@jlowin](https://github.com/jlowin) in [#456](https://github.com/jlowin/fastmcp/pull/456)

### Docs 📚

* Add documentation for tool removal by [@jlowin](https://github.com/jlowin) in [#440](https://github.com/jlowin/fastmcp/pull/440)

### Other Changes 🦾

* Deprecate passing settings to the FastMCP instance by [@jlowin](https://github.com/jlowin) in [#424](https://github.com/jlowin/fastmcp/pull/424)
* Add path prefix to test by [@jlowin](https://github.com/jlowin) in [#432](https://github.com/jlowin/fastmcp/pull/432)

### New Contributors

* [@jbkoh](https://github.com/jbkoh) made their first contribution in [#413](https://github.com/jlowin/fastmcp/pull/413)
* [@davenpi](https://github.com/davenpi) made their first contribution in [#437](https://github.com/jlowin/fastmcp/pull/437)

**Full Changelog**: [v2.3.3...v2.3.4](https://github.com/jlowin/fastmcp/compare/v2.3.3...v2.3.4)
</Update>

<Update label="v2.3.3" description="2024-05-10">

## [v2.3.3: SSE you later](https://github.com/jlowin/fastmcp/releases/tag/v2.3.3)

This is a hotfix for a bug introduced in 2.3.2 that broke SSE servers

### Fixes 🐞

* Fix bug that sets message path and sse path to same value by [@jlowin](https://github.com/jlowin) in [#405](https://github.com/jlowin/fastmcp/pull/405)

### Docs 📚

* Update composition docs by [@jlowin](https://github.com/jlowin) in [#403](https://github.com/jlowin/fastmcp/pull/403)

### Other Changes 🦾

* Add test for no prefix when importing by [@jlowin](https://github.com/jlowin) in [#404](https://github.com/jlowin/fastmcp/pull/404)

**Full Changelog**: [v2.3.2...v2.3.3](https://github.com/jlowin/fastmcp/compare/v2.3.2...v2.3.3)
</Update>

<Update label="v2.3.2" description="2024-05-10">

## [v2.3.2: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.3.2)

### New Features 🎉

* Allow users to pass middleware to starlette app constructors by [@jlowin](https://github.com/jlowin) in [#398](https://github.com/jlowin/fastmcp/pull/398)
* Deprecate transport-specific methods on FastMCP server by [@jlowin](https://github.com/jlowin) in [#401](https://github.com/jlowin/fastmcp/pull/401)

### Docs 📚

* Update CLI docs by [@jlowin](https://github.com/jlowin) in [#402](https://github.com/jlowin/fastmcp/pull/402)

### Other Changes 🦾

* Adding 23 tests for CLI by [@didier-durand](https://github.com/didier-durand) in [#394](https://github.com/jlowin/fastmcp/pull/394)

**Full Changelog**: [v2.3.1...v2.3.2](https://github.com/jlowin/fastmcp/compare/v2.3.1...v2.3.2)
</Update>

<Update label="v2.3.1" description="2024-05-09">

## [v2.3.1: For Good-nests Sake](https://github.com/jlowin/fastmcp/releases/tag/v2.3.1)

This release primarily patches a long-standing bug with nested ASGI SSE servers.

### Fixes 🐞

* Fix tool result serialization when the tool returns a list by [@strawgate](https://github.com/strawgate) in [#379](https://github.com/jlowin/fastmcp/pull/379)
* Ensure FastMCP handles nested SSE and SHTTP apps properly in ASGI frameworks by [@jlowin](https://github.com/jlowin) in [#390](https://github.com/jlowin/fastmcp/pull/390)

### Docs 📚

* Update transport docs by [@jlowin](https://github.com/jlowin) in [#377](https://github.com/jlowin/fastmcp/pull/377)
* Add llms.txt to docs by [@jlowin](https://github.com/jlowin) in [#384](https://github.com/jlowin/fastmcp/pull/384)
* Fixing various text typos by [@didier-durand](https://github.com/didier-durand) in [#385](https://github.com/jlowin/fastmcp/pull/385)

### Other Changes 🦾

* Adding a few tests to Image type by [@didier-durand](https://github.com/didier-durand) in [#387](https://github.com/jlowin/fastmcp/pull/387)
* Adding tests for TimedCache by [@didier-durand](https://github.com/didier-durand) in [#388](https://github.com/jlowin/fastmcp/pull/388)

### New Contributors

* [@didier-durand](https://github.com/didier-durand) made their first contribution in [#385](https://github.com/jlowin/fastmcp/pull/385)

**Full Changelog**: [v2.3.0...v2.3.1](https://github.com/jlowin/fastmcp/compare/v2.3.0...v2.3.1)
</Update>

<Update label="v2.3.0" description="2024-05-08">

## [v2.3.0: Stream Me Up, Scotty](https://github.com/jlowin/fastmcp/releases/tag/v2.3.0)

### New Features 🎉

* Add streaming support for HTTP transport by [@jlowin](https://github.com/jlowin) in [#365](https://github.com/jlowin/fastmcp/pull/365)
* Support streaming HTTP transport in clients by [@jlowin](https://github.com/jlowin) in [#366](https://github.com/jlowin/fastmcp/pull/366)
* Add streaming support to CLI by [@jlowin](https://github.com/jlowin) in [#367](https://github.com/jlowin/fastmcp/pull/367)

### Fixes 🐞

* Fix streaming transport initialization by [@jlowin](https://github.com/jlowin) in [#368](https://github.com/jlowin/fastmcp/pull/368)

### Docs 📚

* Update transport documentation for streaming support by [@jlowin](https://github.com/jlowin) in [#369](https://github.com/jlowin/fastmcp/pull/369)

**Full Changelog**: [v2.2.10...v2.3.0](https://github.com/jlowin/fastmcp/compare/v2.2.10...v2.3.0)
</Update>

<Update label="v2.2.10" description="2024-05-06">

## [v2.2.10: That's JSON Bourne](https://github.com/jlowin/fastmcp/releases/tag/v2.2.10)

### Fixes 🐞

* Disable automatic JSON parsing of tool args by [@jlowin](https://github.com/jlowin) in [#341](https://github.com/jlowin/fastmcp/pull/341)
* Fix prompt test by [@jlowin](https://github.com/jlowin) in [#342](https://github.com/jlowin/fastmcp/pull/342)

### Other Changes 🦾

* Update docs.json by [@jlowin](https://github.com/jlowin) in [#338](https://github.com/jlowin/fastmcp/pull/338)
* Add test coverage + tests on 4 examples by [@alainivars](https://github.com/alainivars) in [#306](https://github.com/jlowin/fastmcp/pull/306)

### New Contributors

* [@alainivars](https://github.com/alainivars) made their first contribution in [#306](https://github.com/jlowin/fastmcp/pull/306)

**Full Changelog**: [v2.2.9...v2.2.10](https://github.com/jlowin/fastmcp/compare/v2.2.9...v2.2.10)
</Update>

<Update label="v2.2.9" description="2024-05-06">

## [v2.2.9: Str-ing the Pot (Hotfix)](https://github.com/jlowin/fastmcp/releases/tag/v2.2.9)

This release is a hotfix for the issue detailed in #330

### Fixes 🐞

* Prevent invalid resource URIs by [@jlowin](https://github.com/jlowin) in [#336](https://github.com/jlowin/fastmcp/pull/336)
* Coerce numbers to str by [@jlowin](https://github.com/jlowin) in [#337](https://github.com/jlowin/fastmcp/pull/337)

### Docs 📚

* Add client badge by [@jlowin](https://github.com/jlowin) in [#327](https://github.com/jlowin/fastmcp/pull/327)
* Update bug.yml by [@jlowin](https://github.com/jlowin) in [#328](https://github.com/jlowin/fastmcp/pull/328)

### Other Changes 🦾

* Update quickstart.mdx example to include import by [@discdiver](https://github.com/discdiver) in [#329](https://github.com/jlowin/fastmcp/pull/329)

### New Contributors

* [@discdiver](https://github.com/discdiver) made their first contribution in [#329](https://github.com/jlowin/fastmcp/pull/329)

**Full Changelog**: [v2.2.8...v2.2.9](https://github.com/jlowin/fastmcp/compare/v2.2.8...v2.2.9)
</Update>

<Update label="v2.2.8" description="2024-05-05">

## [v2.2.8: Parse and Recreation](https://github.com/jlowin/fastmcp/releases/tag/v2.2.8)

### New Features 🎉

* Replace custom parsing with TypeAdapter by [@jlowin](https://github.com/jlowin) in [#314](https://github.com/jlowin/fastmcp/pull/314)
* Handle \*args/\*\*kwargs appropriately for various components by [@jlowin](https://github.com/jlowin) in [#317](https://github.com/jlowin/fastmcp/pull/317)
* Add timeout-graceful-shutdown as a default config for SSE app by [@jlowin](https://github.com/jlowin) in [#323](https://github.com/jlowin/fastmcp/pull/323)
* Ensure prompts return descriptions by [@jlowin](https://github.com/jlowin) in [#325](https://github.com/jlowin/fastmcp/pull/325)

### Fixes 🐞

* Ensure that tool serialization has a graceful fallback by [@jlowin](https://github.com/jlowin) in [#310](https://github.com/jlowin/fastmcp/pull/310)

### Docs 📚

* Update docs for clarity by [@jlowin](https://github.com/jlowin) in [#312](https://github.com/jlowin/fastmcp/pull/312)

### Other Changes 🦾

* Remove is\_async attribute by [@jlowin](https://github.com/jlowin) in [#315](https://github.com/jlowin/fastmcp/pull/315)
* Dry out retrieving context kwarg by [@jlowin](https://github.com/jlowin) in [#316](https://github.com/jlowin/fastmcp/pull/316)

**Full Changelog**: [v2.2.7...v2.2.8](https://github.com/jlowin/fastmcp/compare/v2.2.7...v2.2.8)
</Update>

<Update label="v2.2.7" description="2024-05-03">

## [v2.2.7: You Auth to Know Better](https://github.com/jlowin/fastmcp/releases/tag/v2.2.7)

### New Features 🎉

* use pydantic\_core.to\_json by [@jlowin](https://github.com/jlowin) in [#290](https://github.com/jlowin/fastmcp/pull/290)
* Ensure openapi descriptions are included in tool details by [@jlowin](https://github.com/jlowin) in [#293](https://github.com/jlowin/fastmcp/pull/293)
* Bump mcp to 1.7.1 by [@jlowin](https://github.com/jlowin) in [#298](https://github.com/jlowin/fastmcp/pull/298)
* Add support for tool annotations by [@jlowin](https://github.com/jlowin) in [#299](https://github.com/jlowin/fastmcp/pull/299)
* Add auth support by [@jlowin](https://github.com/jlowin) in [#300](https://github.com/jlowin/fastmcp/pull/300)
* Add low-level methods to client by [@jlowin](https://github.com/jlowin) in [#301](https://github.com/jlowin/fastmcp/pull/301)
* Add method for retrieving current starlette request to FastMCP context by [@jlowin](https://github.com/jlowin) in [#302](https://github.com/jlowin/fastmcp/pull/302)
* get\_starlette\_request → get\_http\_request by [@jlowin](https://github.com/jlowin) in [#303](https://github.com/jlowin/fastmcp/pull/303)
* Support custom Serializer for Tools by [@strawgate](https://github.com/strawgate) in [#308](https://github.com/jlowin/fastmcp/pull/308)
* Support proxy mount by [@jlowin](https://github.com/jlowin) in [#309](https://github.com/jlowin/fastmcp/pull/309)

### Other Changes 🦾

* Improve context injection type checks by [@jlowin](https://github.com/jlowin) in [#291](https://github.com/jlowin/fastmcp/pull/291)
* add readme to smarthome example by [@zzstoatzz](https://github.com/zzstoatzz) in [#294](https://github.com/jlowin/fastmcp/pull/294)

**Full Changelog**: [v2.2.6...v2.2.7](https://github.com/jlowin/fastmcp/compare/v2.2.6...v2.2.7)
</Update>

<Update label="v2.2.6" description="2024-04-30">

## [v2.2.6: The REST is History](https://github.com/jlowin/fastmcp/releases/tag/v2.2.6)

### New Features 🎉

* Added feature : Load MCP server using config by [@sandipan1](https://github.com/sandipan1) in [#260](https://github.com/jlowin/fastmcp/pull/260)
* small typing fixes by [@zzstoatzz](https://github.com/zzstoatzz) in [#237](https://github.com/jlowin/fastmcp/pull/237)
* Expose configurable timeout for OpenAPI by [@jlowin](https://github.com/jlowin) in [#279](https://github.com/jlowin/fastmcp/pull/279)
* Lower websockets pin for compatibility by [@jlowin](https://github.com/jlowin) in [#286](https://github.com/jlowin/fastmcp/pull/286)
* Improve OpenAPI param handling by [@jlowin](https://github.com/jlowin) in [#287](https://github.com/jlowin/fastmcp/pull/287)

### Fixes 🐞

* Ensure openapi tool responses are properly converted by [@jlowin](https://github.com/jlowin) in [#283](https://github.com/jlowin/fastmcp/pull/283)
* Fix OpenAPI examples by [@jlowin](https://github.com/jlowin) in [#285](https://github.com/jlowin/fastmcp/pull/285)
* Fix client docs for advanced features, add tests for logging by [@jlowin](https://github.com/jlowin) in [#284](https://github.com/jlowin/fastmcp/pull/284)

### Other Changes 🦾

* add testing doc by [@jlowin](https://github.com/jlowin) in [#264](https://github.com/jlowin/fastmcp/pull/264)
* #267 Fix openapi template resource to support multiple path parameters by [@jeger-at](https://github.com/jeger-at) in [#278](https://github.com/jlowin/fastmcp/pull/278)

### New Contributors

* [@sandipan1](https://github.com/sandipan1) made their first contribution in [#260](https://github.com/jlowin/fastmcp/pull/260)
* [@jeger-at](https://github.com/jeger-at) made their first contribution in [#278](https://github.com/jlowin/fastmcp/pull/278)

**Full Changelog**: [v2.2.5...v2.2.6](https://github.com/jlowin/fastmcp/compare/v2.2.5...v2.2.6)
</Update>

<Update label="v2.2.5" description="2024-04-26">

## [v2.2.5: Context Switching](https://github.com/jlowin/fastmcp/releases/tag/v2.2.5)

### New Features 🎉

* Add tests for tool return types; improve serialization behavior by [@jlowin](https://github.com/jlowin) in [#262](https://github.com/jlowin/fastmcp/pull/262)
* Support context injection in resources, templates, and prompts (like tools) by [@jlowin](https://github.com/jlowin) in [#263](https://github.com/jlowin/fastmcp/pull/263)

### Docs 📚

* Update wildcards to 2.2.4 by [@jlowin](https://github.com/jlowin) in [#257](https://github.com/jlowin/fastmcp/pull/257)
* Update note in templates docs by [@jlowin](https://github.com/jlowin) in [#258](https://github.com/jlowin/fastmcp/pull/258)
* Significant documentation and test expansion for tool input types by [@jlowin](https://github.com/jlowin) in [#261](https://github.com/jlowin/fastmcp/pull/261)

**Full Changelog**: [v2.2.4...v2.2.5](https://github.com/jlowin/fastmcp/compare/v2.2.4...v2.2.5)
</Update>

<Update label="v2.2.4" description="2024-04-25">

## [v2.2.4: The Wild Side, Actually](https://github.com/jlowin/fastmcp/releases/tag/v2.2.4)

The wildcard URI templates exposed in v2.2.3 were blocked by a server-level check which is removed in this release.

### New Features 🎉

* Allow customization of inspector proxy port, ui port, and version by [@jlowin](https://github.com/jlowin) in [#253](https://github.com/jlowin/fastmcp/pull/253)

### Fixes 🐞

* fix: unintended type convert by [@cutekibry](https://github.com/cutekibry) in [#252](https://github.com/jlowin/fastmcp/pull/252)
* Ensure openapi resources return valid responses by [@jlowin](https://github.com/jlowin) in [#254](https://github.com/jlowin/fastmcp/pull/254)
* Ensure servers expose template wildcards by [@jlowin](https://github.com/jlowin) in [#256](https://github.com/jlowin/fastmcp/pull/256)

### Docs 📚

* Update README.md Grammar error by [@TechWithTy](https://github.com/TechWithTy) in [#249](https://github.com/jlowin/fastmcp/pull/249)

### Other Changes 🦾

* Add resource template tests by [@jlowin](https://github.com/jlowin) in [#255](https://github.com/jlowin/fastmcp/pull/255)

### New Contributors

* [@TechWithTy](https://github.com/TechWithTy) made their first contribution in [#249](https://github.com/jlowin/fastmcp/pull/249)
* [@cutekibry](https://github.com/cutekibry) made their first contribution in [#252](https://github.com/jlowin/fastmcp/pull/252)

**Full Changelog**: [v2.2.3...v2.2.4](https://github.com/jlowin/fastmcp/compare/v2.2.3...v2.2.4)
</Update>

<Update label="v2.2.3" description="2024-04-25">

## [v2.2.3: The Wild Side](https://github.com/jlowin/fastmcp/releases/tag/v2.2.3)

### New Features 🎉

* Add wildcard params for resource templates by [@jlowin](https://github.com/jlowin) in [#246](https://github.com/jlowin/fastmcp/pull/246)

### Docs 📚

* Indicate that Image class is for returns by [@jlowin](https://github.com/jlowin) in [#242](https://github.com/jlowin/fastmcp/pull/242)
* Update mermaid diagram by [@jlowin](https://github.com/jlowin) in [#243](https://github.com/jlowin/fastmcp/pull/243)

### Other Changes 🦾

* update version badges by [@jlowin](https://github.com/jlowin) in [#248](https://github.com/jlowin/fastmcp/pull/248)

**Full Changelog**: [v2.2.2...v2.2.3](https://github.com/jlowin/fastmcp/compare/v2.2.2...v2.2.3)
</Update>

<Update label="v2.2.2" description="2024-04-24">

## [v2.2.2: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v2.2.2)

### New Features 🎉

* Add prompt support by [@jlowin](https://github.com/jlowin) in [#235](https://github.com/jlowin/fastmcp/pull/235)

### Fixes 🐞

* Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#238](https://github.com/jlowin/fastmcp/pull/238)

### Docs 📚

* Update docs for prompts by [@jlowin](https://github.com/jlowin) in [#236](https://github.com/jlowin/fastmcp/pull/236)

### Other Changes 🦾

* Add prompt tests by [@jlowin](https://github.com/jlowin) in [#239](https://github.com/jlowin/fastmcp/pull/239)

**Full Changelog**: [v2.2.1...v2.2.2](https://github.com/jlowin/fastmcp/compare/v2.2.1...v2.2.2)
</Update>

<Update label="v2.2.1" description="2024-04-23">

## [v2.2.1: Template for Success](https://github.com/jlowin/fastmcp/releases/tag/v2.2.1)

### New Features 🎉

* Add resource templates by [@jlowin](https://github.com/jlowin) in [#230](https://github.com/jlowin/fastmcp/pull/230)

### Fixes 🐞

* Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#231](https://github.com/jlowin/fastmcp/pull/231)

### Docs 📚

* Update docs for resource templates by [@jlowin](https://github.com/jlowin) in [#232](https://github.com/jlowin/fastmcp/pull/232)

### Other Changes 🦾

* Add resource template tests by [@jlowin](https://github.com/jlowin) in [#233](https://github.com/jlowin/fastmcp/pull/233)

**Full Changelog**: [v2.2.0...v2.2.1](https://github.com/jlowin/fastmcp/compare/v2.2.0...v2.2.1)
</Update>

<Update label="v2.2.0" description="2024-04-22">

## [v2.2.0: Compose Yourself](https://github.com/jlowin/fastmcp/releases/tag/v2.2.0)

### New Features 🎉

* Add support for mounting FastMCP servers by [@jlowin](https://github.com/jlowin) in [#175](https://github.com/jlowin/fastmcp/pull/175)
* Add support for duplicate behavior == ignore by [@jlowin](https://github.com/jlowin) in [#169](https://github.com/jlowin/fastmcp/pull/169)

### Breaking Changes 🛫

* Refactor MCP composition by [@jlowin](https://github.com/jlowin) in [#176](https://github.com/jlowin/fastmcp/pull/176)

### Docs 📚

* Improve integration documentation by [@jlowin](https://github.com/jlowin) in [#184](https://github.com/jlowin/fastmcp/pull/184)
* Improve documentation by [@jlowin](https://github.com/jlowin) in [#185](https://github.com/jlowin/fastmcp/pull/185)

### Other Changes 🦾

* Add transport kwargs for mcp.run() and fastmcp run by [@jlowin](https://github.com/jlowin) in [#161](https://github.com/jlowin/fastmcp/pull/161)
* Allow resource templates to have optional / excluded arguments by [@jlowin](https://github.com/jlowin) in [#164](https://github.com/jlowin/fastmcp/pull/164)
* Update resources.mdx by [@jlowin](https://github.com/jlowin) in [#165](https://github.com/jlowin/fastmcp/pull/165)

### New Contributors

* [@kongqi404](https://github.com/kongqi404) made their first contribution in [#181](https://github.com/jlowin/fastmcp/pull/181)

**Full Changelog**: [v2.1.2...v2.2.0](https://github.com/jlowin/fastmcp/compare/v2.1.2...v2.2.0)
</Update>

<Update label="v2.1.2" description="2024-04-14">

## [v2.1.2: Copy That, Good Buddy](https://github.com/jlowin/fastmcp/releases/tag/v2.1.2)

The main improvement in this release is a fix that allows FastAPI / OpenAPI-generated servers to be mounted as sub-servers.

### Fixes 🐞

* Ensure objects are copied properly and test mounting fastapi by [@jlowin](https://github.com/jlowin) in [#153](https://github.com/jlowin/fastmcp/pull/153)

### Docs 📚

* Fix broken links in docs by [@jlowin](https://github.com/jlowin) in [#154](https://github.com/jlowin/fastmcp/pull/154)

### Other Changes 🦾

* Update README.md by [@jlowin](https://github.com/jlowin) in [#149](https://github.com/jlowin/fastmcp/pull/149)
* Only apply log config to FastMCP loggers by [@jlowin](https://github.com/jlowin) in [#155](https://github.com/jlowin/fastmcp/pull/155)
* Update pyproject.toml by [@jlowin](https://github.com/jlowin) in [#156](https://github.com/jlowin/fastmcp/pull/156)

**Full Changelog**: [v2.1.1...v2.1.2](https://github.com/jlowin/fastmcp/compare/v2.1.1...v2.1.2)
</Update>

<Update label="v2.1.1" description="2024-04-14">

## [v2.1.1: Doc Holiday](https://github.com/jlowin/fastmcp/releases/tag/v2.1.1)

FastMCP's docs are now available at gofastmcp.com.

### Docs 📚

* Add docs by [@jlowin](https://github.com/jlowin) in [#136](https://github.com/jlowin/fastmcp/pull/136)
* Add docs link to readme by [@jlowin](https://github.com/jlowin) in [#137](https://github.com/jlowin/fastmcp/pull/137)
* Minor docs updates by [@jlowin](https://github.com/jlowin) in [#138](https://github.com/jlowin/fastmcp/pull/138)

### Fixes 🐞

* fix branch name in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#140](https://github.com/jlowin/fastmcp/pull/140)

### Other Changes 🦾

* smart home example by [@zzstoatzz](https://github.com/zzstoatzz) in [#115](https://github.com/jlowin/fastmcp/pull/115)
* Remove mac os tests by [@jlowin](https://github.com/jlowin) in [#142](https://github.com/jlowin/fastmcp/pull/142)
* Expand support for various method interactions by [@jlowin](https://github.com/jlowin) in [#143](https://github.com/jlowin/fastmcp/pull/143)
* Update docs and add\_resource\_fn by [@jlowin](https://github.com/jlowin) in [#144](https://github.com/jlowin/fastmcp/pull/144)
* Update description by [@jlowin](https://github.com/jlowin) in [#145](https://github.com/jlowin/fastmcp/pull/145)
* Support openapi 3.0 and 3.1 by [@jlowin](https://github.com/jlowin) in [#147](https://github.com/jlowin/fastmcp/pull/147)

**Full Changelog**: [v2.1.0...v2.1.1](https://github.com/jlowin/fastmcp/compare/v2.1.0...v2.1.1)
</Update>

<Update label="v2.1.0" description="2024-04-13">

## [v2.1.0: Tag, You're It](https://github.com/jlowin/fastmcp/releases/tag/v2.1.0)

The primary motivation for this release is the fix in #128 for Claude desktop compatibility, but the primary new feature of this release is per-object tags. Currently these are for bookkeeping only but will become useful in future releases.

### New Features 🎉

* Add tags for all core MCP objects by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121)
* Ensure that openapi tags are transferred to MCP objects by [@jlowin](https://github.com/jlowin) in [#124](https://github.com/jlowin/fastmcp/pull/124)

### Fixes 🐞

* Change default mounted tool separator from / to \_ by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128)
* Enter mounted app lifespans by [@jlowin](https://github.com/jlowin) in [#129](https://github.com/jlowin/fastmcp/pull/129)
* Fix CLI that called mcp instead of fastmcp by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128)

### Breaking Changes 🛫

* Changed configuration for duplicate resources/tools/prompts by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121)
* Improve client return types by [@jlowin](https://github.com/jlowin) in [#123](https://github.com/jlowin/fastmcp/pull/123)

### Other Changes 🦾

* Add tests for tags in server decorators by [@jlowin](https://github.com/jlowin) in [#122](https://github.com/jlowin/fastmcp/pull/122)
* Clean up server tests by [@jlowin](https://github.com/jlowin) in [#125](https://github.com/jlowin/fastmcp/pull/125)

**Full Changelog**: [v2.0.0...v2.1.0](https://github.com/jlowin/fastmcp/compare/v2.0.0...v2.1.0)
</Update>

<Update label="v2.0.0" description="2024-04-11">

## [v2.0.0: Second to None](https://github.com/jlowin/fastmcp/releases/tag/v2.0.0)

### New Features 🎉

* Support mounting FastMCP instances as sub-MCPs by [@jlowin](https://github.com/jlowin) in [#99](https://github.com/jlowin/fastmcp/pull/99)
* Add in-memory client for calling FastMCP servers (and tests) by [@jlowin](https://github.com/jlowin) in [#100](https://github.com/jlowin/fastmcp/pull/100)
* Add MCP proxy server by [@jlowin](https://github.com/jlowin) in [#105](https://github.com/jlowin/fastmcp/pull/105)
* Update FastMCP for upstream changes by [@jlowin](https://github.com/jlowin) in [#107](https://github.com/jlowin/fastmcp/pull/107)
* Generate FastMCP servers from OpenAPI specs and FastAPI by [@jlowin](https://github.com/jlowin) in [#110](https://github.com/jlowin/fastmcp/pull/110)
* Reorganize all client / transports by [@jlowin](https://github.com/jlowin) in [#111](https://github.com/jlowin/fastmcp/pull/111)
* Add sampling and roots by [@jlowin](https://github.com/jlowin) in [#117](https://github.com/jlowin/fastmcp/pull/117)

### Fixes 🐞

* Fix bug with tools that return lists by [@jlowin](https://github.com/jlowin) in [#116](https://github.com/jlowin/fastmcp/pull/116)

### Other Changes 🦾

* Add back FastMCP CLI by [@jlowin](https://github.com/jlowin) in [#108](https://github.com/jlowin/fastmcp/pull/108)
* Update Readme for v2 by [@jlowin](https://github.com/jlowin) in [#112](https://github.com/jlowin/fastmcp/pull/112)
* fix deprecation warnings by [@zzstoatzz](https://github.com/zzstoatzz) in [#113](https://github.com/jlowin/fastmcp/pull/113)
* Readme by [@jlowin](https://github.com/jlowin) in [#118](https://github.com/jlowin/fastmcp/pull/118)
* FastMCP 2.0 by [@jlowin](https://github.com/jlowin) in [#119](https://github.com/jlowin/fastmcp/pull/119)

**Full Changelog**: [v1.0...v2.0.0](https://github.com/jlowin/fastmcp/compare/v1.0...v2.0.0)
</Update>

<Update label="v1.0" description="2024-04-11">

## [v1.0: It's Official](https://github.com/jlowin/fastmcp/releases/tag/v1.0)

This release commemorates FastMCP 1.0, which is included in the official Model Context Protocol SDK:

```python
from mcp.server.fastmcp import FastMCP
```

To the best of my knowledge, v1 is identical to the upstream version included with `mcp`.

### Docs 📚

* Update readme to redirect to the official SDK by [@jlowin](https://github.com/jlowin) in [#79](https://github.com/jlowin/fastmcp/pull/79)

### Other Changes 🦾

* fix: use Mount instead of Route for SSE message handling by [@samihamine](https://github.com/samihamine) in [#77](https://github.com/jlowin/fastmcp/pull/77)

### New Contributors

* [@samihamine](https://github.com/samihamine) made their first contribution in [#77](https://github.com/jlowin/fastmcp/pull/77)

**Full Changelog**: [v0.4.1...v1.0](https://github.com/jlowin/fastmcp/compare/v0.4.1...v1.0)
</Update>

<Update label="v0.4.1" description="2024-12-09">

## [v0.4.1: String Theory](https://github.com/jlowin/fastmcp/releases/tag/v0.4.1)

### Fixes 🐞

* fix: handle strings containing numbers correctly by [@sd2k](https://github.com/sd2k) in [#63](https://github.com/jlowin/fastmcp/pull/63)

### Docs 📚

* patch: Update pyproject.toml license by [@leonkozlowski](https://github.com/leonkozlowski) in [#67](https://github.com/jlowin/fastmcp/pull/67)

### Other Changes 🦾

* Avoid new try\_eval\_type unavailable with older pydantic by [@jurasofish](https://github.com/jurasofish) in [#57](https://github.com/jlowin/fastmcp/pull/57)
* Decorator typing by [@jurasofish](https://github.com/jurasofish) in [#56](https://github.com/jlowin/fastmcp/pull/56)

### New Contributors

* [@leonkozlowski](https://github.com/leonkozlowski) made their first contribution in [#67](https://github.com/jlowin/fastmcp/pull/67)

**Full Changelog**: [v0.4.0...v0.4.1](https://github.com/jlowin/fastmcp/compare/v0.4.0...v0.4.1)
</Update>

<Update label="v0.4.0" description="2024-12-05">

## [v0.4.0: Nice to MIT You](https://github.com/jlowin/fastmcp/releases/tag/v0.4.0)

This is a relatively small release in terms of features, but the version is bumped to 0.4 to reflect that the code is being relicensed from Apache 2.0 to MIT. This is to facilitate FastMCP's inclusion in the official MCP SDK.

### New Features 🎉

* Add pyright + tests by [@jlowin](https://github.com/jlowin) in [#52](https://github.com/jlowin/fastmcp/pull/52)
* add pgvector memory example by [@zzstoatzz](https://github.com/zzstoatzz) in [#49](https://github.com/jlowin/fastmcp/pull/49)

### Fixes 🐞

* fix: use stderr for logging by [@sd2k](https://github.com/sd2k) in [#51](https://github.com/jlowin/fastmcp/pull/51)

### Docs 📚

* Update ai-labeler.yml by [@jlowin](https://github.com/jlowin) in [#48](https://github.com/jlowin/fastmcp/pull/48)
* Relicense from Apache 2.0 to MIT by [@jlowin](https://github.com/jlowin) in [#54](https://github.com/jlowin/fastmcp/pull/54)

### Other Changes 🦾

* fix warning and flake by [@zzstoatzz](https://github.com/zzstoatzz) in [#47](https://github.com/jlowin/fastmcp/pull/47)

### New Contributors

* [@sd2k](https://github.com/sd2k) made their first contribution in [#51](https://github.com/jlowin/fastmcp/pull/51)

**Full Changelog**: [v0.3.5...v0.4.0](https://github.com/jlowin/fastmcp/compare/v0.3.5...v0.4.0)
</Update>

<Update label="v0.3.5" description="2024-12-03">

## [v0.3.5: Windows of Opportunity](https://github.com/jlowin/fastmcp/releases/tag/v0.3.5)

This release is highlighted by the ability to handle complex JSON objects as MCP inputs and improved Windows compatibility.

### New Features 🎉

* Set up multiple os tests by [@jlowin](https://github.com/jlowin) in [#44](https://github.com/jlowin/fastmcp/pull/44)
* Changes to accomodate windows users. by [@justjoehere](https://github.com/justjoehere) in [#42](https://github.com/jlowin/fastmcp/pull/42)
* Handle complex inputs by [@jurasofish](https://github.com/jurasofish) in [#31](https://github.com/jlowin/fastmcp/pull/31)

### Docs 📚

* Make AI labeler more conservative by [@jlowin](https://github.com/jlowin) in [#46](https://github.com/jlowin/fastmcp/pull/46)

### Other Changes 🦾

* Additional Windows Fixes for Dev running and for importing modules in a server by [@justjoehere](https://github.com/justjoehere) in [#43](https://github.com/jlowin/fastmcp/pull/43)

### New Contributors

* [@justjoehere](https://github.com/justjoehere) made their first contribution in [#42](https://github.com/jlowin/fastmcp/pull/42)
* [@jurasofish](https://github.com/jurasofish) made their first contribution in [#31](https://github.com/jlowin/fastmcp/pull/31)

**Full Changelog**: [v0.3.4...v0.3.5](https://github.com/jlowin/fastmcp/compare/v0.3.4...v0.3.5)
</Update>

<Update label="v0.3.4" description="2024-12-02">

## [v0.3.4: URL's Well That Ends Well](https://github.com/jlowin/fastmcp/releases/tag/v0.3.4)

### Fixes 🐞

* Handle missing config file when installing by [@jlowin](https://github.com/jlowin) in [#37](https://github.com/jlowin/fastmcp/pull/37)
* Remove BaseURL reference and use AnyURL by [@jlowin](https://github.com/jlowin) in [#40](https://github.com/jlowin/fastmcp/pull/40)

**Full Changelog**: [v0.3.3...v0.3.4](https://github.com/jlowin/fastmcp/compare/v0.3.3...v0.3.4)
</Update>

<Update label="v0.3.3" description="2024-12-02">

## [v0.3.3: Dependence Day](https://github.com/jlowin/fastmcp/releases/tag/v0.3.3)

### New Features 🎉

* Surge example by [@zzstoatzz](https://github.com/zzstoatzz) in [#29](https://github.com/jlowin/fastmcp/pull/29)
* Support Python dependencies in Server by [@jlowin](https://github.com/jlowin) in [#34](https://github.com/jlowin/fastmcp/pull/34)

### Docs 📚

* add `Contributing` section to README by [@zzstoatzz](https://github.com/zzstoatzz) in [#32](https://github.com/jlowin/fastmcp/pull/32)

**Full Changelog**: [v0.3.2...v0.3.3](https://github.com/jlowin/fastmcp/compare/v0.3.2...v0.3.3)
</Update>

<Update label="v0.3.2" date="2024-12-01" description="Green with ENVy">

## [v0.3.2: Green with ENVy](https://github.com/jlowin/fastmcp/releases/tag/v0.3.2)

### New Features 🎉

* Support env vars when installing by [@jlowin](https://github.com/jlowin) in [#27](https://github.com/jlowin/fastmcp/pull/27)

### Docs 📚

* Remove top level env var by [@jlowin](https://github.com/jlowin) in [#28](https://github.com/jlowin/fastmcp/pull/28)

**Full Changelog**: [v0.3.1...v0.3.2](https://github.com/jlowin/fastmcp/compare/v0.3.1...v0.3.2)
</Update>

<Update label="v0.3.1" description="2024-12-01">

## [v0.3.1](https://github.com/jlowin/fastmcp/releases/tag/v0.3.1)

### New Features 🎉

* Update README.md by [@jlowin](https://github.com/jlowin) in [#23](https://github.com/jlowin/fastmcp/pull/23)
* add rich handler and dotenv loading for settings by [@zzstoatzz](https://github.com/zzstoatzz) in [#22](https://github.com/jlowin/fastmcp/pull/22)
* print exception when server can't start by [@jlowin](https://github.com/jlowin) in [#25](https://github.com/jlowin/fastmcp/pull/25)

### Docs 📚

* Update README.md by [@jlowin](https://github.com/jlowin) in [#24](https://github.com/jlowin/fastmcp/pull/24)

### Other Changes 🦾

* Remove log by [@jlowin](https://github.com/jlowin) in [#26](https://github.com/jlowin/fastmcp/pull/26)

**Full Changelog**: [v0.3.0...v0.3.1](https://github.com/jlowin/fastmcp/compare/v0.3.0...v0.3.1)
</Update>

<Update label="v0.3.0" description="2024-12-01">

## [v0.3.0: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v0.3.0)

### New Features 🎉

* Update README by [@jlowin](https://github.com/jlowin) in [#3](https://github.com/jlowin/fastmcp/pull/3)
* Make log levels strings by [@jlowin](https://github.com/jlowin) in [#4](https://github.com/jlowin/fastmcp/pull/4)
* Make content method a function by [@jlowin](https://github.com/jlowin) in [#5](https://github.com/jlowin/fastmcp/pull/5)
* Add template support by [@jlowin](https://github.com/jlowin) in [#6](https://github.com/jlowin/fastmcp/pull/6)
* Refactor resources module by [@jlowin](https://github.com/jlowin) in [#7](https://github.com/jlowin/fastmcp/pull/7)
* Clean up cli imports by [@jlowin](https://github.com/jlowin) in [#8](https://github.com/jlowin/fastmcp/pull/8)
* Prepare to list templates by [@jlowin](https://github.com/jlowin) in [#11](https://github.com/jlowin/fastmcp/pull/11)
* Move image to separate module by [@jlowin](https://github.com/jlowin) in [#9](https://github.com/jlowin/fastmcp/pull/9)
* Add support for request context, progress, logging, etc. by [@jlowin](https://github.com/jlowin) in [#12](https://github.com/jlowin/fastmcp/pull/12)
* Add context tests and better runtime loads by [@jlowin](https://github.com/jlowin) in [#13](https://github.com/jlowin/fastmcp/pull/13)
* Refactor tools + resourcemanager by [@jlowin](https://github.com/jlowin) in [#14](https://github.com/jlowin/fastmcp/pull/14)
* func → fn everywhere by [@jlowin](https://github.com/jlowin) in [#15](https://github.com/jlowin/fastmcp/pull/15)
* Add support for prompts by [@jlowin](https://github.com/jlowin) in [#16](https://github.com/jlowin/fastmcp/pull/16)
* Create LICENSE by [@jlowin](https://github.com/jlowin) in [#18](https://github.com/jlowin/fastmcp/pull/18)
* Update cli file spec by [@jlowin](https://github.com/jlowin) in [#19](https://github.com/jlowin/fastmcp/pull/19)
* Update readmeUpdate README by [@jlowin](https://github.com/jlowin) in [#20](https://github.com/jlowin/fastmcp/pull/20)
* Use hatchling for version by [@jlowin](https://github.com/jlowin) in [#21](https://github.com/jlowin/fastmcp/pull/21)

### Other Changes 🦾

* Add echo server by [@jlowin](https://github.com/jlowin) in [#1](https://github.com/jlowin/fastmcp/pull/1)
* Add github workflows by [@jlowin](https://github.com/jlowin) in [#2](https://github.com/jlowin/fastmcp/pull/2)
* typing updates by [@zzstoatzz](https://github.com/zzstoatzz) in [#17](https://github.com/jlowin/fastmcp/pull/17)

### New Contributors

* [@jlowin](https://github.com/jlowin) made their first contribution in [#1](https://github.com/jlowin/fastmcp/pull/1)
* [@zzstoatzz](https://github.com/zzstoatzz) made their first contribution in [#17](https://github.com/jlowin/fastmcp/pull/17)

**Full Changelog**: [v0.2.0...v0.3.0](https://github.com/jlowin/fastmcp/compare/v0.2.0...v0.3.0)
</Update>

<Update label="v0.2.0" description="2024-11-30">

## [v0.2.0](https://github.com/jlowin/fastmcp/releases/tag/v0.2.0)

**Full Changelog**: [v0.1.0...v0.2.0](https://github.com/jlowin/fastmcp/compare/v0.1.0...v0.2.0)
</Update>

<Update label="v0.1.0" description="2024-11-30">

## [v0.1.0](https://github.com/jlowin/fastmcp/releases/tag/v0.1.0)

The very first release of FastMCP! 🎉

**Full Changelog**: [Initial commits](https://github.com/jlowin/fastmcp/commits/v0.1.0)
</Update>

================
File: docs/docs.json
================
{
  "$schema": "https://mintlify.com/docs.json",
  "appearance": {
    "default": "system",
    "strict": false
  },
  "background": {
    "color": {
      "dark": "#222831",
      "light": "#EEEEEE"
    },
    "decoration": "windows"
  },
  "banner": {
    "content": "FastMCP Cloud is here! [Join the beta](https://fastmcp.link/x0Kyhy2)."
  },
  "colors": {
    "dark": "#f72585",
    "light": "#4cc9f0",
    "primary": "#2d00f7"
  },
  "description": "The fast, Pythonic way to build MCP servers and clients.",
  "favicon": {
    "dark": "/assets/favicon.ico",
    "light": "/assets/favicon.ico"
  },
  "footer": {
    "socials": {
      "bluesky": "https://bsky.app/profile/jlowin.dev",
      "github": "https://github.com/jlowin/fastmcp",
      "x": "https://x.com/jlowin"
    }
  },
  "integrations": {
    "ga4": {
      "measurementId": "G-64R5W1TJXG"
    }
  },
  "name": "FastMCP",
  "navbar": {
    "primary": {
      "href": "https://github.com/jlowin/fastmcp",
      "type": "github"
    }
  },
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "anchors": [
          {
            "anchor": "Documentation",
            "groups": [
              {
                "group": "Get Started",
                "pages": [
                  "getting-started/welcome",
                  "getting-started/installation",
                  "getting-started/quickstart"
                ]
              },
              {
                "group": "Servers",
                "pages": [
                  "servers/server",
                  {
                    "group": "Core Components",
                    "icon": "toolbox",
                    "pages": [
                      "servers/tools",
                      "servers/resources",
                      "servers/prompts"
                    ]
                  },
                  {
                    "group": "Advanced Features",
                    "icon": "stars",
                    "pages": [
                      "servers/context",
                      "servers/elicitation",
                      "servers/logging",
                      "servers/progress",
                      "servers/sampling"
                    ]
                  },
                  {
                    "group": "Authentication",
                    "icon": "shield-check",
                    "pages": ["servers/auth/bearer"]
                  },
                  "servers/middleware",
                  "servers/openapi",
                  "servers/proxy",
                  "servers/composition",
                  {
                    "group": "Deployment",
                    "icon": "upload",
                    "pages": ["deployment/running-server", "deployment/asgi"]
                  }
                ]
              },
              {
                "group": "Clients",
                "pages": [
                  "clients/client",
                  {
                    "group": "Core Operations",
                    "icon": "handshake",
                    "pages": [
                      "clients/tools",
                      "clients/resources",
                      "clients/prompts"
                    ]
                  },
                  {
                    "group": "Advanced Features",
                    "icon": "stars",
                    "pages": [
                      "clients/elicitation",
                      "clients/logging",
                      "clients/progress",
                      "clients/sampling",
                      "clients/messages",
                      "clients/roots"
                    ]
                  },
                  "clients/transports",
                  {
                    "group": "Authentication",
                    "icon": "user-shield",
                    "pages": ["clients/auth/oauth", "clients/auth/bearer"]
                  }
                ]
              },
              {
                "group": "Integrations",
                "pages": [
                  "integrations/anthropic",
                  "integrations/chatgpt",
                  "integrations/claude-code",
                  "integrations/claude-desktop",
                  "integrations/gemini",
                  "integrations/openai",
                  "integrations/eunomia-authorization",
                  "integrations/contrib"
                ]
              },
              {
                "group": "Patterns",
                "pages": [
                  "patterns/tool-transformation",
                  "patterns/decorating-methods",
                  "patterns/http-requests",
                  "patterns/testing",
                  "patterns/cli"
                ]
              },
              {
                "group": "Tutorials",
                "pages": [
                  "tutorials/mcp",
                  "tutorials/create-mcp-server",
                  "tutorials/rest-api"
                ]
              }
            ],
            "icon": "book"
          },
          {
            "anchor": "What's New",
            "pages": ["updates", "changelog"]
          },
          {
            "anchor": "Community",
            "icon": "users",
            "pages": ["community/showcase"]
          }
        ]
      },
      {
        "tab": "SDK Reference",
        "anchors": [
          {
            "anchor": "Python SDK",
            "icon": "python",
            "pages": [
              "python-sdk/fastmcp-exceptions",
              "python-sdk/fastmcp-settings",
              {
                "group": "fastmcp.cli",
                "pages": [
                  "python-sdk/fastmcp-cli-__init__",
                  "python-sdk/fastmcp-cli-claude",
                  "python-sdk/fastmcp-cli-cli",
                  "python-sdk/fastmcp-cli-run"
                ]
              },
              {
                "group": "fastmcp.client",
                "pages": [
                  "python-sdk/fastmcp-client-__init__",
                  {
                    "group": "auth",
                    "pages": [
                      "python-sdk/fastmcp-client-auth-__init__",
                      "python-sdk/fastmcp-client-auth-bearer",
                      "python-sdk/fastmcp-client-auth-oauth"
                    ]
                  },
                  "python-sdk/fastmcp-client-client",
                  "python-sdk/fastmcp-client-logging",
                  "python-sdk/fastmcp-client-oauth_callback",
                  "python-sdk/fastmcp-client-progress",
                  "python-sdk/fastmcp-client-roots",
                  "python-sdk/fastmcp-client-sampling",
                  "python-sdk/fastmcp-client-transports"
                ]
              },
              {
                "group": "fastmcp.prompts",
                "pages": [
                  "python-sdk/fastmcp-prompts-__init__",
                  "python-sdk/fastmcp-prompts-prompt",
                  "python-sdk/fastmcp-prompts-prompt_manager"
                ]
              },
              {
                "group": "fastmcp.resources",
                "pages": [
                  "python-sdk/fastmcp-resources-__init__",
                  "python-sdk/fastmcp-resources-resource",
                  "python-sdk/fastmcp-resources-resource_manager",
                  "python-sdk/fastmcp-resources-template",
                  "python-sdk/fastmcp-resources-types"
                ]
              },
              {
                "group": "fastmcp.server",
                "pages": [
                  "python-sdk/fastmcp-server-__init__",
                  {
                    "group": "auth",
                    "pages": [
                      "python-sdk/fastmcp-server-auth-__init__",
                      "python-sdk/fastmcp-server-auth-auth",
                      {
                        "group": "providers",
                        "pages": [
                          "python-sdk/fastmcp-server-auth-providers-__init__",
                          "python-sdk/fastmcp-server-auth-providers-bearer",
                          "python-sdk/fastmcp-server-auth-providers-bearer_env",
                          "python-sdk/fastmcp-server-auth-providers-in_memory"
                        ]
                      }
                    ]
                  },
                  "python-sdk/fastmcp-server-context",
                  "python-sdk/fastmcp-server-dependencies",
                  "python-sdk/fastmcp-server-http",
                  {
                    "group": "middleware",
                    "pages": [
                      "python-sdk/fastmcp-server-middleware-__init__",
                      "python-sdk/fastmcp-server-middleware-error_handling",
                      "python-sdk/fastmcp-server-middleware-logging",
                      "python-sdk/fastmcp-server-middleware-middleware",
                      "python-sdk/fastmcp-server-middleware-rate_limiting",
                      "python-sdk/fastmcp-server-middleware-timing"
                    ]
                  },
                  "python-sdk/fastmcp-server-openapi",
                  "python-sdk/fastmcp-server-proxy",
                  "python-sdk/fastmcp-server-server"
                ]
              },
              {
                "group": "fastmcp.tools",
                "pages": [
                  "python-sdk/fastmcp-tools-__init__",
                  "python-sdk/fastmcp-tools-tool",
                  "python-sdk/fastmcp-tools-tool_manager",
                  "python-sdk/fastmcp-tools-tool_transform"
                ]
              },
              {
                "group": "fastmcp.utilities",
                "pages": [
                  "python-sdk/fastmcp-utilities-__init__",
                  "python-sdk/fastmcp-utilities-cache",
                  "python-sdk/fastmcp-utilities-components",
                  "python-sdk/fastmcp-utilities-exceptions",
                  "python-sdk/fastmcp-utilities-http",
                  "python-sdk/fastmcp-utilities-inspect",
                  "python-sdk/fastmcp-utilities-json_schema",
                  "python-sdk/fastmcp-utilities-logging",
                  "python-sdk/fastmcp-utilities-mcp_config",
                  "python-sdk/fastmcp-utilities-openapi",
                  "python-sdk/fastmcp-utilities-tests",
                  "python-sdk/fastmcp-utilities-types"
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "redirects": [
    {
      "destination": "/servers/proxy",
      "source": "/patterns/proxy"
    },
    {
      "destination": "/servers/composition",
      "source": "/patterns/composition"
    }
  ],
  "search": {
    "prompt": "Search the docs..."
  },
  "theme": "mint"
}

================
File: docs/updates.mdx
================
---
title: "FastMCP Updates"
sidebarTitle: "Updates"
icon: "sparkles"
tag: NEW
---

<Update label="FastMCP 2.9" description="June 23, 2025" tags={["Releases", "Blog Posts"]}>
<Card 
title="FastMCP 2.9: MCP-Native Middleware" href="https://www.jlowin.dev/blog/fastmcp-2-9-middleware" 
img="https://jlowin.dev/_image?href=%2F_astro%2Fhero.BkVTdeBk.jpg&w=1200&h=630&f=png" 
cta="Read more"  
>
FastMCP 2.9 is a major release that, among other things, introduces two important features that push beyond the basic MCP protocol. 

🤝 *MCP Middleware* brings a flexible middleware system for intercepting and controlling server operations - think authentication, logging, rate limiting, and custom business logic without touching core protocol code. 

✨ *Server-side type conversion* for prompts solves a major developer pain point: while MCP requires string arguments, your functions can now work with native Python types like lists and dictionaries, with automatic conversion handling the complexity.

These features transform FastMCP from a simple protocol implementation into a powerful framework for building sophisticated MCP applications. Combined with the new `File` utility for binary data and improvements to authentication and serialization, this release makes FastMCP significantly more flexible and developer-friendly while maintaining full protocol compliance.
</Card>
</Update>

<Update label="FastMCP 2.8" description="June 11, 2025" tags={["Releases", "Blog Posts"]}>
<Card 
title="FastMCP 2.8: Transform and Roll Out" href="https://www.jlowin.dev/blog/fastmcp-2-8-tool-transformation" 
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.su3kspkP.png&w=1000&h=500&f=webp" 
cta="Read more"  
>
FastMCP 2.8 is here, and it's all about taking control of your tools.

This release is packed with new features for curating the perfect LLM experience:

🛠️ Tool Transformation

The headline feature lets you wrap any tool—from your own code, a third-party library, or an OpenAPI spec—to create an enhanced, LLM-friendly version. You can rename arguments, rewrite descriptions, and hide parameters without touching the original code.

This feature was developed in close partnership with Bill Easton. As Bill brilliantly [put it](https://www.linkedin.com/posts/williamseaston_huge-thanks-to-william-easton-for-providing-activity-7338011349525983232-Mw6T?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAAd6d0B3uL9zpCsq9eYWKi3HIvb8eN_r_Q), "Tool transformation flips Prompt Engineering on its head: stop writing tool-friendly LLM prompts and start providing LLM-friendly tools."

🏷️ Component Control

Now that you're transforming tools, you need a way to hide the old ones! In FastMCP 2.8 you can programmatically enable/disable any component, and for everyone who's been asking what FastMCP's tags are for—they finally have a purpose! You can now use tags to declaratively filter which components are exposed to your clients.

🚀 Pragmatic by Default

Lastly, to ensure maximum compatibility with the ecosystem, we've made the pragmatic decision to default all OpenAPI routes to Tools, making your entire API immediately accessible to any tool-using agent. When the industry catches up and supports resources, we'll restore the old default -- but no reason you should do extra work before OpenAI, Anthropic, or Google!

</Card>
</Update>

<Update label="FastMCP 2.7" description="June 6, 2025" tags={["Releases"]}>
<Card 
title="FastMCP 2.7: Pare Programming" href="https://github.com/jlowin/fastmcp/releases/tag/v2.7.0" 
img="assets/updates/release-2-7.png" 
cta="Read the release notes"  
>
FastMCP 2.7 has been released!

Most notably, it introduces the highly requested (and Pythonic) "naked" decorator usage:

```python {3}
mcp = FastMCP()

@mcp.tool
def add(a: int, b: int) -> int:
    return a + b
```

In addition, decorators now return the objects they create, instead of the decorated function. This is an important usability enhancement.

The bulk of the update is focused on improving the FastMCP internals, including a few breaking internal changes to private APIs. A number of functions that have clung on since 1.0 are now deprecated.
</Card>
</Update>



<Update label="FastMCP 2.6" description="June 2, 2025" tags={["Releases", "Blog Posts"]}>
<Card 
title="FastMCP 2.6: Blast Auth" href="https://www.jlowin.dev/blog/fastmcp-2-6" 
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Bsu8afiw.png&w=1000&h=500&f=webp" 
cta="Read more"  
>
FastMCP 2.6 is here!

This release introduces first-class authentication for MCP servers and clients, including pragmatic Bearer token support and seamless OAuth 2.1 integration. This release aligns with how major AI platforms are adopting MCP today, making it easier than ever to securely connect your tools to real-world AI models. Dive into the update and secure your stack with minimal friction.
</Card>
</Update>

<Update description="May 21, 2025" label="Vibe-Testing" tags={["Blog Posts", "Tutorials"]}>
<Card
title="Stop Vibe-Testing Your MCP Server"
href="https://www.jlowin.dev/blog/stop-vibe-testing-mcp-servers"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.BUPy9I9c.png&w=1000&h=500&f=webp"
cta="Read more"
>

Your tests are bad and you should feel bad.

Stop vibe-testing your MCP server through LLM guesswork. FastMCP 2.0 introduces in-memory testing for fast, deterministic, and fully Pythonic validation of your MCP logic—no network, no subprocesses, no vibes.

</Card>
</Update>


<Update description="May 8, 2025" label="10,000 Stars" tags={["Blog Posts"]}>
<Card
title="Reflecting on FastMCP at 10k stars 🌟"
href="https://www.jlowin.dev/blog/fastmcp-2-10k-stars"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Cnvci9Q_.png&w=1000&h=500&f=webp"
cta="Read more"
>

In just six weeks since its relaunch, FastMCP has surpassed 10,000 GitHub stars—becoming the fastest-growing OSS project in our orbit. What started as a personal itch has become the backbone of Python-based MCP servers, powering a rapidly expanding ecosystem. While the protocol itself evolves, FastMCP continues to lead with clarity, developer experience, and opinionated tooling. Here’s to what’s next.

</Card>
</Update>

<Update description="May 8, 2025" label="FastMCP 2.3" tags={["Blog Posts", "Releases"]}>
<Card
title="Now Streaming: FastMCP 2.3"
href="https://www.jlowin.dev/blog/fastmcp-2-3-streamable-http"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.M_hv6gEB.png&w=1000&h=500&f=webp"
cta="Read more"
>

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.

</Card>
</Update>

<Update description="April 23, 2025" label="Proxy Servers" tags={["Blog Posts", "Tutorials"]}>
<Card
title="MCP Proxy Servers with FastMCP 2.0"
href="https://www.jlowin.dev/blog/fastmcp-proxy"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Frobot-hero.DpmAqgui.png&w=1000&h=500&f=webp"
cta="Read more"
>

Even AI needs a good travel adapter 🔌


FastMCP now supports proxying arbitrary MCP servers, letting you run a local FastMCP instance that transparently forwards requests to any remote or third-party server—regardless of transport. This enables transport bridging (e.g., stdio ⇄ SSE), simplified client configuration, and powerful gateway patterns. Proxies are fully composable with other FastMCP servers, letting you mount or import them just like local servers. Use `FastMCP.from_client()` to wrap any backend in a clean, Pythonic proxy.
</Card>
</Update>

<Update label="FastMCP 2.0" description="April 16, 2025" tags={["Releases", "Blog Posts"]}>
<Card
title="Introducing FastMCP 2.0 🚀"
href="https://www.jlowin.dev/blog/fastmcp-2"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.DpbmGNrr.png&w=1000&h=500&f=webp"
cta="Read more"
>

This major release reimagines FastMCP as a full ecosystem platform, with powerful new features for composition, integration, and client interaction. You can now compose local and remote servers, proxy arbitrary MCP servers (with transport translation), and generate MCP servers from OpenAPI or FastAPI apps. A new client infrastructure supports advanced workflows like LLM sampling. 

FastMCP 2.0 builds on the success of v1 with a cleaner, more flexible foundation—try it out today!
</Card>
</Update>



<Update label="Official SDK" description="December 3, 2024" tags={["Announcements"]}>
<Card
title="FastMCP is joining the official MCP Python SDK!"
href="https://bsky.app/profile/jlowin.dev/post/3lch4xk5cf22c"
icon="sparkles"
cta="Read the announcement"
>
FastMCP 1.0 will become part of the official MCP Python SDK!
</Card>
</Update>



<Update label="FastMCP 1.0" description="December 1, 2024" tags={["Releases", "Blog Posts"]}>
<Card
title="Introducing FastMCP 🚀"
href="https://www.jlowin.dev/blog/introducing-fastmcp"
img="https://www.jlowin.dev/_image?href=%2F_astro%2Ffastmcp.Bep7YlTw.png&w=1000&h=500&f=webp"
cta="Read more"
>
Because life's too short for boilerplate.

This is where it all started. FastMCP’s launch post introduced a clean, Pythonic way to build MCP servers without the protocol overhead. Just write functions; FastMCP handles the rest. What began as a weekend project quickly became the foundation of a growing ecosystem.
</Card>
</Update>

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/__init__.py
================
"""Private ATProto implementation module."""
⋮----
__all__ = [

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/_client.py
================
"""ATProto client management."""
⋮----
_client: Client | None = None
def get_client() -> Client
⋮----
"""Get or create an authenticated ATProto client."""
⋮----
_client = Client()

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/_posts.py
================
"""Unified posting functionality."""
⋮----
"""Create a unified post with optional features.
    Args:
        text: Post text (max 300 chars)
        images: URLs of images to attach (max 4)
        image_alts: Alt text for images
        links: Links to embed in rich text
        mentions: User mentions to embed
        reply_to: URI of post to reply to
        reply_root: URI of thread root (defaults to reply_to)
        quote: URI of post to quote
    """
⋮----
client = get_client()
facets = []
embed = None
reply_ref = None
# Handle rich text facets (links and mentions)
⋮----
facets = _build_facets(text, links, mentions, client)
# Handle replies
⋮----
reply_ref = _build_reply_ref(reply_to, reply_root, client)
# Handle quotes and images
⋮----
# Quote with images - create record with media embed
embed = _build_quote_with_images_embed(quote, images, image_alts, client)
⋮----
# Quote only
embed = _build_quote_embed(quote, client)
⋮----
# Images only - use send_images for proper handling
⋮----
# Send the post
post = client.send_post(
⋮----
"""Build facets for rich text formatting."""
⋮----
# Process links
⋮----
start = text.find(link["text"])
⋮----
end = start + len(link["text"])
⋮----
# Process mentions
⋮----
display_text = mention.get("display_text") or f"@{mention['handle']}"
start = text.find(display_text)
⋮----
end = start + len(display_text)
# Resolve handle to DID
resolved = client.app.bsky.actor.search_actors(
⋮----
did = resolved.actors[0].did
⋮----
def _build_reply_ref(reply_to: str, reply_root: str | None, client)
⋮----
"""Build reply reference."""
# Get parent post to extract CID
parent_post = client.app.bsky.feed.get_posts(params={"uris": [reply_to]})
⋮----
parent_cid = parent_post.posts[0].cid
parent_ref = models.ComAtprotoRepoStrongRef.Main(uri=reply_to, cid=parent_cid)
# If no root_uri provided, parent is the root
⋮----
root_ref = parent_ref
⋮----
# Get root post CID
root_post = client.app.bsky.feed.get_posts(params={"uris": [reply_root]})
⋮----
root_cid = root_post.posts[0].cid
root_ref = models.ComAtprotoRepoStrongRef.Main(uri=reply_root, cid=root_cid)
⋮----
def _build_quote_embed(quote_uri: str, client)
⋮----
"""Build quote embed."""
# Get the post to quote
quoted_post = client.app.bsky.feed.get_posts(params={"uris": [quote_uri]})
⋮----
# Create strong ref for the quoted post
quoted_cid = quoted_post.posts[0].cid
quoted_ref = models.ComAtprotoRepoStrongRef.Main(uri=quote_uri, cid=quoted_cid)
# Create the embed
⋮----
"""Build quote embed with images."""
⋮----
# Get the quoted post
⋮----
# Download and upload images
images = []
alts = image_alts or [""] * len(image_urls)
⋮----
response = httpx.get(url, follow_redirects=True)
⋮----
# Upload to blob storage
upload = client.upload_blob(response.content)
⋮----
# Create record with media embed
⋮----
"""Send post with images using the client's send_images method."""
⋮----
# Ensure alt_texts has same length as images
⋮----
image_alts = [""] * len(image_urls)
⋮----
image_data = []
alts = []
for i, url in enumerate(image_urls[:4]):  # Max 4 images
# Download image (follow redirects)
⋮----
# Send post with images
# Note: send_images doesn't support facets or reply_to directly
# So we need to use send_post with manual image upload if we have those
⋮----
# Manual image upload
⋮----
upload = client.upload_blob(data)
⋮----
embed = models.AppBskyEmbedImages.Main(images=images)
⋮----
# Use simple send_images
post = client.send_images(
⋮----
def create_thread(posts: list[ThreadPost]) -> ThreadResult
⋮----
"""Create a thread of posts with automatic linking.
    Args:
        posts: List of posts to create as a thread. First post is the root.
    """
⋮----
post_uris = []
root_uri = None
parent_uri = None
⋮----
# First post is the root
⋮----
result = create_post(
⋮----
root_uri = result["uri"]
parent_uri = root_uri
⋮----
# Small delay to ensure post is indexed
⋮----
# Subsequent posts reply to the previous one
⋮----
parent_uri = result["uri"]
⋮----
# Small delay between posts

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/_profile.py
================
"""Profile-related operations."""
⋮----
def get_profile_info() -> ProfileInfo
⋮----
"""Get profile information for the authenticated user."""
⋮----
client = get_client()
profile = client.get_profile(client.me.did)

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/_read.py
================
"""Read-only operations for timeline, search, and notifications."""
⋮----
def fetch_timeline(limit: int = 10) -> TimelineResult
⋮----
"""Fetch the authenticated user's timeline."""
⋮----
client = get_client()
timeline = client.get_timeline(limit=limit)
posts = []
⋮----
post = feed_view.post
⋮----
def search_for_posts(query: str, limit: int = 10) -> SearchResult
⋮----
"""Search for posts containing specific text."""
⋮----
search_results = client.app.bsky.feed.search_posts(
⋮----
def fetch_notifications(limit: int = 10) -> NotificationsResult
⋮----
"""Fetch recent notifications."""
⋮----
notifs = client.app.bsky.notification.list_notifications(
notifications = []

================
File: examples/atproto_mcp/src/atproto_mcp/_atproto/_social.py
================
"""Social actions like follow, like, and repost."""
⋮----
def follow_user_by_handle(handle: str) -> FollowResult
⋮----
"""Follow a user by their handle."""
⋮----
client = get_client()
# Search for the user to get their DID
results = client.app.bsky.actor.search_actors(params={"q": handle, "limit": 1})
⋮----
actor = results.actors[0]
# Create the follow
follow = client.follow(actor.did)
⋮----
def like_post_by_uri(uri: str) -> LikeResult
⋮----
"""Like a post by its AT URI."""
⋮----
# Parse the URI to get the components
# URI format: at://did:plc:xxx/app.bsky.feed.post/yyy
parts = uri.replace("at://", "").split("/")
⋮----
# Get the post to retrieve its CID
post = client.app.bsky.feed.get_posts(params={"uris": [uri]})
⋮----
cid = post.posts[0].cid
# Now like the post with both URI and CID
like = client.like(uri, cid)
⋮----
def repost_by_uri(uri: str) -> RepostResult
⋮----
"""Repost a post by its AT URI."""
⋮----
# Now repost with both URI and CID
repost = client.repost(uri, cid)

================
File: examples/atproto_mcp/src/atproto_mcp/__init__.py
================
__all__ = ["settings"]

================
File: examples/atproto_mcp/src/atproto_mcp/__main__.py
================
def main()

================
File: examples/atproto_mcp/src/atproto_mcp/server.py
================
"""ATProto MCP Server - Public API exposing Bluesky tools and resources."""
⋮----
atproto_mcp = FastMCP(
# Resources - read-only operations
⋮----
@atproto_mcp.resource("atproto://profile/status")
def atproto_status() -> ProfileInfo
⋮----
"""Check the status of the ATProto connection and current user profile."""
⋮----
@atproto_mcp.resource("atproto://timeline")
def get_timeline() -> TimelineResult
⋮----
"""Get the authenticated user's timeline feed."""
⋮----
@atproto_mcp.resource("atproto://notifications")
def get_notifications() -> NotificationsResult
⋮----
"""Get recent notifications for the authenticated user."""
⋮----
# Tools - actions that modify state
⋮----
"""Create a post with optional rich features like images, quotes, replies, and rich text.
    Examples:
        - Simple post: post("Hello world!")
        - With image: post("Check this out!", images=["https://example.com/img.jpg"])
        - Reply: post("I agree!", reply_to="at://did/app.bsky.feed.post/123")
        - Quote: post("Great point!", quote="at://did/app.bsky.feed.post/456")
        - Rich text: post("Check out example.com", links=[{"text": "example.com", "url": "https://example.com"}])
    """
⋮----
"""Follow a user by their handle."""
⋮----
"""Like a post by its AT URI."""
⋮----
"""Repost a post by its AT URI."""
⋮----
"""Search for posts containing specific text."""
⋮----
"""Create a thread of posts with automatic linking.
    The first post becomes the root of the thread, and each subsequent post
    replies to the previous one, maintaining the thread structure.
    Example:
        create_thread([
            {"text": "Starting a thread about Python 🧵"},
            {"text": "Python is great for rapid development"},
            {"text": "And the ecosystem is amazing!", "images": ["https://example.com/python.jpg"]}
        ])
    """

================
File: examples/atproto_mcp/src/atproto_mcp/settings.py
================
class Settings(BaseSettings)
⋮----
model_config = SettingsConfigDict(env_file=[".env"], extra="ignore")
atproto_handle: str = Field(default=...)
atproto_password: str = Field(default=...)
atproto_pds_url: str = Field(default="https://bsky.social")
atproto_notifications_default_limit: int = Field(default=10)
atproto_timeline_default_limit: int = Field(default=10)
atproto_search_default_limit: int = Field(default=10)
settings = Settings()

================
File: examples/atproto_mcp/src/atproto_mcp/types.py
================
"""Type definitions for ATProto MCP server."""
⋮----
class ProfileInfo(TypedDict)
⋮----
"""Profile information response."""
connected: bool
handle: str | None
display_name: str | None
did: str | None
followers: int | None
following: int | None
posts: int | None
error: str | None
class PostResult(TypedDict)
⋮----
"""Result of creating a post."""
success: bool
uri: str | None
cid: str | None
text: str | None
created_at: str | None
⋮----
class Post(TypedDict)
⋮----
"""A single post."""
author: str
⋮----
likes: int
reposts: int
replies: int
uri: str
cid: str
class TimelineResult(TypedDict)
⋮----
"""Timeline fetch result."""
⋮----
count: int
posts: list[Post]
⋮----
class SearchResult(TypedDict)
⋮----
"""Search result."""
⋮----
query: str
⋮----
class Notification(TypedDict)
⋮----
"""A single notification."""
reason: str
author: str | None
is_read: bool
indexed_at: str
⋮----
class NotificationsResult(TypedDict)
⋮----
"""Notifications fetch result."""
⋮----
notifications: list[Notification]
⋮----
class FollowResult(TypedDict)
⋮----
"""Result of following a user."""
⋮----
class LikeResult(TypedDict)
⋮----
"""Result of liking a post."""
⋮----
liked_uri: str | None
like_uri: str | None
⋮----
class RepostResult(TypedDict)
⋮----
"""Result of reposting."""
⋮----
reposted_uri: str | None
repost_uri: str | None
⋮----
class RichTextLink(TypedDict)
⋮----
"""A link in rich text."""
text: str
url: str
class RichTextMention(TypedDict)
⋮----
"""A mention in rich text."""
handle: str
display_text: str | None
class ThreadPost(TypedDict, total=False)
⋮----
"""A post in a thread."""
text: str  # Required
images: list[str] | None
image_alts: list[str] | None
links: list[RichTextLink] | None
mentions: list[RichTextMention] | None
quote: str | None
class ThreadResult(TypedDict)
⋮----
"""Result of creating a thread."""
⋮----
thread_uri: str | None  # URI of the first post
post_uris: list[str]
post_count: int

================
File: examples/atproto_mcp/demo.py
================
"""Demo script showing all ATProto MCP server capabilities."""
⋮----
async def main(enable_posting: bool = False)
⋮----
# 1. Check connection status (resource)
⋮----
result = await client.read_resource("atproto://profile/status")
status: ProfileInfo = (
⋮----
# 2. Get timeline
⋮----
result = await client.read_resource("atproto://timeline")
timeline: TimelineResult = (
⋮----
post = timeline["posts"][0]
⋮----
save_uri = post["uri"]  # Save for later interactions
⋮----
save_uri = None
# 3. Search for posts
⋮----
result = await client.call_tool("search", {"query": "Bluesky", "limit": 5})
search: SearchResult = (
⋮----
# 4. Get notifications
⋮----
result = await client.read_resource("atproto://notifications")
notifs: NotificationsResult = (
⋮----
unread = sum(1 for n in notifs["notifications"] if not n["is_read"])
⋮----
# 5. Demo posting capabilities
⋮----
# a. Simple post
⋮----
result = await client.call_tool(
post_result: PostResult = json.loads(result[0].text) if result else {}
⋮----
simple_uri = post_result["uri"]
⋮----
simple_uri = None
# b. Post with rich text (link and mention)
⋮----
# c. Reply to a post
⋮----
# d. Quote post
⋮----
# e. Post with image
⋮----
# f. Quote with image (advanced)
⋮----
# g. Social actions
⋮----
# Like
result = await client.call_tool("like", {"uri": save_uri})
⋮----
# Repost
result = await client.call_tool("repost", {"uri": save_uri})
⋮----
# Follow
⋮----
# h. Thread creation (new!)
⋮----
thread_result = json.loads(result[0].text)
⋮----
# 6. Show available capabilities
⋮----
parser = argparse.ArgumentParser(description="ATProto MCP Server Demo")
⋮----
args = parser.parse_args()

================
File: examples/atproto_mcp/pyproject.toml
================
[project]
name = "atproto-mcp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }]
requires-python = ">=3.10"
dependencies = [
    "fastmcp>=0.8.0",
    "atproto@git+https://github.com/MarshalX/atproto.git@refs/pull/605/head",
    "pydantic-settings>=2.0.0",
    "websockets>=15.0.1",
    "httpx>=0.27.0",
]

[project.scripts]
atproto-mcp = "atproto_mcp.__main__:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.metadata]
allow-direct-references = true

================
File: examples/atproto_mcp/README.md
================
# ATProto MCP Server

This example demonstrates a FastMCP server that provides tools and resources for interacting with the AT Protocol (Bluesky).

## Features

### Resources (Read-only)

- **atproto://profile/status**: Get connection status and profile information
- **atproto://timeline**: Retrieve your timeline feed
- **atproto://notifications**: Get recent notifications

### Tools (Actions)

- **post**: Create posts with rich features (text, images, quotes, replies, links, mentions)
- **create_thread**: Post multi-part threads with automatic linking
- **search**: Search for posts by query
- **follow**: Follow users by handle
- **like**: Like posts by URI
- **repost**: Share posts by URI

## Setup

1. Create a `.env` file in the root directory with your Bluesky credentials:

```bash
ATPROTO_HANDLE=your.handle@bsky.social
ATPROTO_PASSWORD=your-app-password
ATPROTO_PDS_URL=https://bsky.social  # optional, defaults to bsky.social
```

2. Install and run the server:

```bash
# Install dependencies
uv pip install -e .

# Run the server
uv run atproto-mcp
```

## The Unified Post Tool

The `post` tool is a single, flexible interface for all posting needs:

```python
async def post(
    text: str,                          # Required: Post content
    images: list[str] = None,           # Optional: Image URLs (max 4)
    image_alts: list[str] = None,       # Optional: Alt text for images
    links: list[RichTextLink] = None,   # Optional: Embedded links
    mentions: list[RichTextMention] = None,  # Optional: User mentions
    reply_to: str = None,               # Optional: Reply to post URI
    reply_root: str = None,             # Optional: Thread root URI
    quote: str = None,                  # Optional: Quote post URI
)
```

### Usage Examples

```python
from fastmcp import Client
from atproto_mcp.server import atproto_mcp

async def demo():
    async with Client(atproto_mcp) as client:
        # Simple post
        await client.call_tool("post", {
            "text": "Hello from FastMCP!"
        })
        
        # Post with image
        await client.call_tool("post", {
            "text": "Beautiful sunset! 🌅",
            "images": ["https://example.com/sunset.jpg"],
            "image_alts": ["Sunset over the ocean"]
        })
        
        # Reply to a post
        await client.call_tool("post", {
            "text": "Great point!",
            "reply_to": "at://did:plc:xxx/app.bsky.feed.post/yyy"
        })
        
        # Quote post
        await client.call_tool("post", {
            "text": "This is important:",
            "quote": "at://did:plc:xxx/app.bsky.feed.post/yyy"
        })
        
        # Rich text with links and mentions
        await client.call_tool("post", {
            "text": "Check out FastMCP by @alternatebuild.dev",
            "links": [{"text": "FastMCP", "url": "https://github.com/jlowin/fastmcp"}],
            "mentions": [{"handle": "alternatebuild.dev", "display_text": "@alternatebuild.dev"}]
        })
        
        # Advanced: Quote with image
        await client.call_tool("post", {
            "text": "Adding visual context:",
            "quote": "at://did:plc:xxx/app.bsky.feed.post/yyy",
            "images": ["https://example.com/chart.png"]
        })
        
        # Advanced: Reply with rich text
        await client.call_tool("post", {
            "text": "I agree! See this article for more info",
            "reply_to": "at://did:plc:xxx/app.bsky.feed.post/yyy",
            "links": [{"text": "this article", "url": "https://example.com/article"}]
        })
        
        # Create a thread
        await client.call_tool("create_thread", {
            "posts": [
                {"text": "Starting a thread about Python 🧵"},
                {"text": "Python is great for rapid prototyping"},
                {"text": "And the ecosystem is amazing!", "images": ["https://example.com/python.jpg"]}
            ]
        })
```

## AI Assistant Use Cases

The unified API enables natural AI assistant interactions:

- **"Reply to that post with these findings"** → Uses `reply_to` with rich text
- **"Share this article with commentary"** → Uses `quote` with the article link
- **"Post this chart with explanation"** → Uses `images` with descriptive text
- **"Start a thread about AI safety"** → Uses `create_thread` for automatic linking

## Architecture

The server is organized as:
- `server.py` - Public API with resources and tools
- `_atproto/` - Private implementation module
  - `_client.py` - ATProto client management
  - `_posts.py` - Unified posting logic
  - `_profile.py` - Profile operations
  - `_read.py` - Timeline, search, notifications
  - `_social.py` - Follow, like, repost
- `types.py` - TypedDict definitions
- `settings.py` - Configuration management

## Running the Demo

```bash
# Run demo (read-only)
uv run python demo.py

# Run demo with posting enabled
uv run python demo.py --post
```

## Security Note

Store your Bluesky credentials securely in environment variables. Never commit credentials to version control.

================
File: examples/smart_home/src/smart_home/lights/hue_utils.py
================
def _get_bridge() -> Bridge | None
⋮----
"""Attempts to connect to the Hue bridge using settings."""
⋮----
# Broad exception to catch potential connection issues
# TODO: Add more specific logging or error handling
⋮----
"""Creates a standardized error response for phue2 operations."""
base_info = {"target": light_or_group, "operation": operation, "success": False}

================
File: examples/smart_home/src/smart_home/lights/server.py
================
class HueAttributes(TypedDict, total=False)
⋮----
"""TypedDict for optional light attributes."""
on: NotRequired[Annotated[bool, Field(description="on/off state")]]
bri: NotRequired[Annotated[int, Field(ge=0, le=254, description="brightness")]]
hue: NotRequired[
xy: NotRequired[Annotated[list[float], Field(description="xy color coordinates")]]
ct: NotRequired[
alert: NotRequired[Literal["none", "select", "lselect"]]
effect: NotRequired[Literal["none", "colorloop"]]
transitiontime: NotRequired[Annotated[int, Field(description="deciseconds")]]
lights_mcp = FastMCP(
⋮----
@lights_mcp.tool
def read_all_lights() -> list[str]
⋮----
"""Lists the names of all available Hue lights using phue2."""
⋮----
light_dict = bridge.get_light_objects("list")
⋮----
# Simplified error handling for list return type
⋮----
# --- Tools ---
⋮----
@lights_mcp.tool
def toggle_light(light_name: str, state: bool) -> dict[str, Any]
⋮----
"""Turns a specific light on (true) or off (false) using phue2."""
⋮----
result = bridge.set_light(light_name, "on", state)
⋮----
@lights_mcp.tool
def set_brightness(light_name: str, brightness: int) -> dict[str, Any]
⋮----
"""Sets the brightness of a specific light (0-254) using phue2."""
⋮----
# Keep specific input validation error here
⋮----
result = bridge.set_light(light_name, "bri", brightness)
⋮----
@lights_mcp.tool
def list_groups() -> list[str]
⋮----
"""Lists the names of all available Hue light groups."""
⋮----
# phue2 get_group() returns a dict {id: {details}} including name
groups = bridge.get_group()
⋮----
@lights_mcp.tool
def list_scenes() -> dict[str, list[str]] | list[str]
⋮----
"""Lists Hue scenes, grouped by the light group they belong to.
    Returns:
        dict[str, list[str]]: A dictionary mapping group names to a list of scene names within that group.
        list[str]: An error message list if the bridge connection fails or an error occurs.
    """
⋮----
scenes_data = bridge.get_scene()  # Returns dict {scene_id: {details...}}
groups_data = bridge.get_group()  # Returns dict {group_id: {details...}}
# Create a lookup for group name by group ID
group_id_to_name = {gid: ginfo["name"] for gid, ginfo in groups_data.items()}
scenes_by_group: dict[str, list[str]] = {}
⋮----
scene_name = scene_details.get("name")
# Scenes might be associated with a group via 'group' key or lights
# Using 'group' key if available is more direct for group scenes
group_id = scene_details.get("group")
⋮----
group_name = group_id_to_name[group_id]
⋮----
# Avoid duplicate scene names within a group listing (though unlikely)
⋮----
# Sort scenes within each group for consistent output
⋮----
# Return error as list to match other list-returning tools on error
⋮----
@lights_mcp.tool
def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]
⋮----
"""Activates a specific scene within a specified light group, verifying the scene belongs to the group."""
⋮----
# 1. Find the target group ID
groups_data = bridge.get_group()
target_group_id = None
⋮----
target_group_id = gid
⋮----
# 2. Find the target scene and check its group association
scenes_data = bridge.get_scene()
scene_found = False
scene_in_correct_group = False
⋮----
scene_found = True
# Check if this scene is associated with the target group ID
⋮----
scene_in_correct_group = True
break  # Found the scene in the correct group
⋮----
# 3. Activate the scene (now that we've verified it)
result = bridge.run_scene(group_name=group_name, scene_name=scene_name)
⋮----
# This case might indicate the scene/group exists but activation failed internally
⋮----
# Handle potential errors during bridge communication or data parsing
⋮----
@lights_mcp.tool
def set_light_attributes(light_name: str, attributes: HueAttributes) -> dict[str, Any]
⋮----
"""Sets multiple attributes (e.g., hue, sat, bri, ct, xy, transitiontime) for a specific light."""
⋮----
# Basic validation (more specific validation could be added)
⋮----
result = bridge.set_light(light_name, dict(attributes))
⋮----
# ValueError might occur for invalid attribute values
⋮----
@lights_mcp.tool
def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str, Any]
⋮----
"""Sets multiple attributes for all lights within a specific group."""
⋮----
result = bridge.set_group(group_name, dict(attributes))
⋮----
@lights_mcp.tool
def list_lights_by_group() -> dict[str, list[str]] | list[str]
⋮----
"""Lists Hue lights, grouped by the room/group they belong to.
    Returns:
        dict[str, list[str]]: A dictionary mapping group names to a list of light names within that group.
        list[str]: An error message list if the bridge connection fails or an error occurs.
    """
⋮----
groups_data = bridge.get_group()  # dict {group_id: {details}}
lights_data = bridge.get_light_objects("id")  # dict {light_id: {details}}
lights_by_group: dict[str, list[str]] = {}
⋮----
group_name = group_details.get("name")
light_ids = group_details.get("lights", [])
⋮----
light_names = []
⋮----
# phue uses string IDs for lights in group, but int IDs in get_light_objects
light_id_int = int(light_id)
⋮----
light_name = lights_data[light_id_int].name

================
File: examples/smart_home/src/smart_home/__init__.py
================
__all__ = ["settings"]

================
File: examples/smart_home/src/smart_home/__main__.py
================
def main()

================
File: examples/smart_home/src/smart_home/hub.py
================
hub_mcp = FastMCP(
# Mount the lights service under the 'hue' prefix
⋮----
# Add a status check for the hub
⋮----
@hub_mcp.tool
def hub_status() -> str
⋮----
"""Checks the status of the main hub and connections."""
⋮----
bridge = Bridge(
⋮----
# Add mounting points for other services later
# hub_mcp.mount("thermo", thermostat_mcp)

================
File: examples/smart_home/src/smart_home/settings.py
================
class Settings(BaseSettings)
⋮----
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
hue_bridge_ip: IPvAnyAddress = Field(default=...)
hue_bridge_username: str = Field(default=...)
settings = Settings()

================
File: examples/smart_home/pyproject.toml
================
[project]
name = "smart-home"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }]
requires-python = ">=3.12"
dependencies = ["fastmcp@git+https://github.com/jlowin/fastmcp.git", "phue2"]

[project.scripts]
smart-home = "smart_home.__main__:main"

[dependency-groups]
dev = ["ruff", "ipython"]


[build-system]
requires = ["uv_build"]
build-backend = "uv_build"

================
File: examples/smart_home/README.md
================
# smart home mcp server

```bash
cd examples/smart_home
mcp install src/smart_home/hub.py:hub_mcp -f .env
```
where `.env` contains the following:
```
HUE_BRIDGE_IP=<your hue bridge ip>
HUE_BRIDGE_USERNAME=<your hue bridge username>
```

```bash
open -a Claude
```

================
File: examples/complex_inputs.py
================
"""
FastMCP Complex inputs Example
Demonstrates validation via pydantic with complex models.
"""
⋮----
mcp = FastMCP("Shrimp Tank")
class ShrimpTank(BaseModel)
⋮----
class Shrimp(BaseModel)
⋮----
name: Annotated[str, Field(max_length=10)]
shrimp: list[Shrimp]
⋮----
# You can use pydantic Field in function signatures for validation.
⋮----
"""List all shrimp names in the tank"""

================
File: examples/config_server.py
================
"""
Simple example showing FastMCP server with command line argument support.
Usage:
    fastmcp run examples/config_server.py -- --name MyServer --debug
"""
⋮----
parser = argparse.ArgumentParser(description="Simple configurable MCP server")
⋮----
args = parser.parse_args()
server_name = args.name
⋮----
mcp = FastMCP(server_name)
⋮----
@mcp.tool
def get_status() -> dict[str, str | bool]
⋮----
"""Get the current server configuration and status."""
⋮----
@mcp.tool
def echo_message(message: str) -> str
⋮----
"""Echo a message, with debug info if debug mode is enabled."""

================
File: examples/desktop.py
================
"""
FastMCP Desktop Example
A simple example that exposes the desktop directory as a resource.
"""
⋮----
# Create server
mcp = FastMCP("Demo")
⋮----
@mcp.resource("dir://desktop")
def desktop() -> list[str]
⋮----
"""List the files in the user's desktop"""
desktop = Path.home() / "Desktop"
⋮----
# Add a dynamic greeting resource
⋮----
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str
⋮----
"""Get a personalized greeting"""
⋮----
@mcp.tool
def add(a: int, b: int) -> int
⋮----
"""Add two numbers"""

================
File: examples/echo.py
================
"""
FastMCP Echo Server
"""
⋮----
# Create server
mcp = FastMCP("Echo Server")
⋮----
@mcp.tool
def echo_tool(text: str) -> str
⋮----
"""Echo the input text"""
⋮----
@mcp.resource("echo://static")
def echo_resource() -> str
⋮----
@mcp.resource("echo://{text}")
def echo_template(text: str) -> str
⋮----
@mcp.prompt("echo")
def echo_prompt(text: str) -> str

================
File: examples/get_file.py
================
def create_server()
⋮----
mcp = FastMCP(name="File Demo", instructions="Get files from the server or URL.")
⋮----
@mcp.tool()
    async def get_test_file_from_server(path: str = "requirements.txt") -> File
⋮----
"""
        Get a test file from the server. If the path is not provided, it defaults to 'requirements.txt'.
        """
⋮----
"""
        Get a test PDF file from a URL. If the URL is not provided, it defaults to a sample PDF.
        """
⋮----
pdf_data = await response.read()

================
File: examples/in_memory_proxy_example.py
================
"""
This example demonstrates how to set up and use an in-memory FastMCP proxy.
It illustrates the pattern:
1. Create an original FastMCP server with some tools.
2. Create a proxy FastMCP server using ``FastMCP.as_proxy(original_server)``.
3. Use another Client to connect to the proxy server (in-memory) and interact with the original server's tools through the proxy.
"""
⋮----
class EchoService
⋮----
"""A simple service to demonstrate with"""
def echo(self, message: str) -> str
async def main()
⋮----
# 1. Original Server Setup
⋮----
original_server = FastMCP("OriginalEchoServer")
⋮----
# 2. Proxy Server Creation
⋮----
proxy_server = FastMCP.as_proxy(original_server, name="InMemoryProxy")
⋮----
# 3. Interacting via Proxy
⋮----
tools = await final_client.list_tools()
⋮----
message_to_echo = "Hello, simplified proxied world!"
⋮----
result = await final_client.call_tool("echo", {"message": message_to_echo})

================
File: examples/memory.py
================
# /// script
# dependencies = ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector", "fastmcp"]
# ///
# uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector fastmcp
"""
Recursive memory system inspired by the human brain's clustering of memories.
Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient similarity search.
"""
⋮----
from pgvector.asyncpg import register_vector  # Import register_vector
⋮----
MAX_DEPTH = 5
SIMILARITY_THRESHOLD = 0.7
DECAY_FACTOR = 0.99
REINFORCEMENT_FACTOR = 1.1
DEFAULT_LLM_MODEL = "openai:gpt-4o"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
mcp = FastMCP(
DB_DSN = "postgresql://postgres:postgres@localhost:54320/memory_db"
# reset memory with rm ~/.fastmcp/{USER}/memory/*
PROFILE_DIR = (
⋮----
def cosine_similarity(a: list[float], b: list[float]) -> float
⋮----
a_array = np.array(a, dtype=np.float64)
b_array = np.array(b, dtype=np.float64)
⋮----
agent = Agent(
result = await agent.run(user_prompt, deps=deps)
⋮----
@dataclass
class Deps
⋮----
openai: AsyncOpenAI
pool: asyncpg.Pool
async def get_db_pool() -> asyncpg.Pool
⋮----
async def init(conn)
pool = await asyncpg.create_pool(DB_DSN, init=init)
⋮----
class MemoryNode(BaseModel)
⋮----
id: int | None = None
content: str
summary: str = ""
importance: float = 1.0
access_count: int = 0
timestamp: float = Field(
embedding: list[float]
⋮----
@classmethod
    async def from_content(cls, content: str, deps: Deps)
⋮----
embedding = await get_embedding(content, deps)
⋮----
async def save(self, deps: Deps)
⋮----
result = await conn.fetchrow(
⋮----
async def merge_with(self, other: Self, deps: Deps)
⋮----
# Delete the merged node from the database
⋮----
def get_effective_importance(self)
async def get_embedding(text: str, deps: Deps) -> list[float]
⋮----
embedding_response = await deps.openai.embeddings.create(
⋮----
async def delete_memory(memory_id: int, deps: Deps)
async def add_memory(content: str, deps: Deps)
⋮----
new_memory = await MemoryNode.from_content(content, deps)
⋮----
similar_memories = await find_similar_memories(new_memory.embedding, deps)
⋮----
async def find_similar_memories(embedding: list[float], deps: Deps) -> list[MemoryNode]
⋮----
rows = await conn.fetch(
memories = [
⋮----
async def update_importance(user_embedding: list[float], deps: Deps)
⋮----
memory_embedding = row["embedding"]
similarity = cosine_similarity(user_embedding, memory_embedding)
⋮----
new_importance = row["importance"] * REINFORCEMENT_FACTOR
new_access_count = row["access_count"] + 1
⋮----
new_importance = row["importance"] * DECAY_FACTOR
new_access_count = row["access_count"]
⋮----
async def prune_memories(deps: Deps)
async def display_memory_tree(deps: Deps) -> str
⋮----
result = ""
⋮----
effective_importance = row["importance"] * (
summary = row["summary"] or row["content"]
⋮----
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
⋮----
@mcp.tool
async def read_profile() -> str
⋮----
profile = await display_memory_tree(deps)
⋮----
async def initialize_database()
⋮----
pool = await asyncpg.create_pool(
⋮----
pool = await asyncpg.create_pool(DB_DSN)

================
File: examples/mount_example.py
================
"""Example of mounting FastMCP apps together.
This example demonstrates how to mount FastMCP apps together using
the ToolManager's import_tools functionality. It shows how to:
1. Create sub-applications for different domains
2. Mount those sub-applications to a main application
3. Access tools with prefixed names and resources with prefixed URIs
"""
⋮----
# Weather sub-application
weather_app = FastMCP("Weather App")
⋮----
@weather_app.tool
def get_weather_forecast(location: str) -> str
⋮----
"""Get the weather forecast for a location."""
⋮----
@weather_app.resource(uri="weather://forecast")
async def weather_data()
⋮----
"""Return current weather data."""
⋮----
# News sub-application
news_app = FastMCP("News App")
⋮----
@news_app.tool
def get_news_headlines() -> list[str]
⋮----
"""Get the latest news headlines."""
⋮----
@news_app.resource(uri="news://headlines")
async def news_data()
⋮----
"""Return latest news data."""
⋮----
# Main application
app = FastMCP(
⋮----
@app.tool
def check_app_status() -> dict[str, str]
⋮----
"""Check the status of the main application."""
⋮----
# Mount sub-applications
⋮----
async def get_server_details()
⋮----
"""Print information about mounted resources."""
# Print available tools
tools = await app.get_tools()
⋮----
# Print available resources
⋮----
# Distinguish between native and imported resources
# Native resources would be those directly in the main app (not prefixed)
resources = await app.get_resources()
native_resources = [
# Imported resources - categorized by source app
weather_resources = [
news_resources = [
⋮----
# Let's try to access resources using the prefixed URI
weather_data = await app._mcp_read_resource(uri="weather://weather/forecast")
⋮----
# First run our async function to display info
⋮----
# Then start the server (uncomment to run the server)

================
File: examples/sampling.py
================
"""
Example of using sampling to request an LLM completion via Marvin
"""
⋮----
# -- Create a server that sends a sampling request to the LLM
mcp = FastMCP("Sampling Example")
⋮----
@mcp.tool
async def example_tool(prompt: str, context: Context) -> str
⋮----
"""Sample a completion from the LLM."""
response = await context.sample(
⋮----
# -- Create a client that can handle the sampling request
⋮----
async def run()
⋮----
result = await client.call_tool(

================
File: examples/screenshot.py
================
"""
FastMCP Screenshot Example
Give Claude a tool to capture and view screenshots.
"""
⋮----
# Create server
mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
⋮----
@mcp.tool
def take_screenshot() -> Image
⋮----
"""
    Take a screenshot of the user's screen and return it as an image. Use
    this tool anytime the user wants you to look at something they're doing.
    """
⋮----
buffer = io.BytesIO()
# if the file exceeds ~1MB, it will be rejected by Claude
screenshot = pyautogui.screenshot()

================
File: examples/serializer.py
================
# Define a simple custom serializer
def custom_dict_serializer(data: Any) -> str
server = FastMCP(name="CustomSerializerExample", tool_serializer=custom_dict_serializer)
⋮----
@server.tool
def get_example_data() -> dict
⋮----
"""Returns some example data."""
⋮----
async def example_usage()
⋮----
result = await server._mcp_call_tool("get_example_data", {})

================
File: examples/simple_echo.py
================
"""
FastMCP Echo Server
"""
⋮----
# Create server
mcp = FastMCP("Echo Server")
⋮----
@mcp.tool
def echo(text: str) -> str
⋮----
"""Echo the input text"""

================
File: examples/tags_example.py
================
"""
Example demonstrating RouteMap tags functionality.
This example shows how to use the tags parameter in RouteMap
to selectively route OpenAPI endpoints based on their tags.
"""
⋮----
# Create a FastAPI app with tagged endpoints
app = FastAPI(title="Tagged API Example")
⋮----
@app.get("/users", tags=["users", "public"])
async def get_users()
⋮----
"""Get all users - public endpoint"""
⋮----
@app.post("/users", tags=["users", "admin"])
async def create_user(name: str)
⋮----
"""Create a user - admin only"""
⋮----
@app.get("/admin/stats", tags=["admin", "internal"])
async def get_admin_stats()
⋮----
"""Get admin statistics - internal use"""
⋮----
@app.get("/health", tags=["public"])
async def health_check()
⋮----
"""Public health check"""
⋮----
@app.get("/metrics")
async def get_metrics()
⋮----
"""Metrics endpoint with no tags"""
⋮----
async def main()
⋮----
"""Demonstrate different tag-based routing strategies."""
⋮----
# Strategy 1: Convert admin-tagged routes to tools
mcp1 = FastMCP.from_fastapi(
tools = await mcp1.get_tools()
resources = await mcp1.get_resources()
⋮----
# Strategy 2: Exclude internal routes entirely
mcp2 = FastMCP.from_fastapi(
tools = await mcp2.get_tools()
resources = await mcp2.get_resources()
⋮----
# Strategy 3: Routes matching both pattern AND tags
mcp3 = FastMCP.from_fastapi(
⋮----
# Admin routes under /admin path -> tools
⋮----
# Public routes -> tools
⋮----
tools = await mcp3.get_tools()
resources = await mcp3.get_resources()
⋮----
# Strategy 4: Routes must have ALL specified tags
mcp4 = FastMCP.from_fastapi(
⋮----
# Routes with BOTH "users" AND "admin" tags -> tools
⋮----
tools = await mcp4.get_tools()
resources = await mcp4.get_resources()

================
File: examples/text_me.py
================
# /// script
# dependencies = ["fastmcp"]
# ///
"""
FastMCP Text Me Server
--------------------------------
This defines a simple FastMCP server that sends a text message to a phone number via https://surgemsg.com/.
To run this example, create a `.env` file with the following values:
SURGE_API_KEY=...
SURGE_ACCOUNT_ID=...
SURGE_MY_PHONE_NUMBER=...
SURGE_MY_FIRST_NAME=...
SURGE_MY_LAST_NAME=...
Visit https://surgemsg.com/ and click "Get Started" to obtain these values.
"""
⋮----
class SurgeSettings(BaseSettings)
⋮----
model_config: SettingsConfigDict = SettingsConfigDict(
api_key: str
account_id: str
my_phone_number: Annotated[
my_first_name: str
my_last_name: str
# Create server
mcp = FastMCP("Text me")
surge_settings = SurgeSettings()  # type: ignore
⋮----
@mcp.tool(name="textme", description="Send a text message to me")
def text_me(text_content: str) -> str
⋮----
"""Send a text message to a phone number via https://surgemsg.com/"""
⋮----
response = client.post(

================
File: src/fastmcp/cli/__init__.py
================
"""FastMCP CLI package."""

================
File: src/fastmcp/cli/claude.py
================
"""Claude app integration utilities."""
⋮----
logger = get_logger(__name__)
def get_claude_config_path() -> Path | None
⋮----
"""Get the Claude config directory based on platform."""
⋮----
path = Path(Path.home(), "AppData", "Roaming", "Claude")
⋮----
path = Path(Path.home(), "Library", "Application Support", "Claude")
⋮----
path = Path(
⋮----
"""Add or update a FastMCP server in Claude's configuration.
    Args:
        file_spec: Path to the server file, optionally with :object suffix
        server_name: Name for the server in Claude's config
        with_editable: Optional directory to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables. These are merged with
            any existing variables, with new values taking precedence.
    Raises:
        RuntimeError: If Claude Desktop's config directory is not found, indicating
            Claude Desktop may not be installed or properly set up.
    """
config_dir = get_claude_config_path()
⋮----
config_file = config_dir / "claude_desktop_config.json"
⋮----
config = json.loads(config_file.read_text())
⋮----
# Always preserve existing env vars and merge with new ones
⋮----
existing_env = config["mcpServers"][server_name]["env"]
⋮----
# New vars take precedence over existing ones
env_vars = {**existing_env, **env_vars}
⋮----
env_vars = existing_env
# Build uv run command
args = ["run"]
# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
⋮----
# Add all packages with --with
⋮----
# Convert file path to absolute before adding to command
# Split off any :object suffix first
⋮----
file_spec = f"{Path(file_path).resolve()}:{server_object}"
⋮----
file_spec = str(Path(file_spec).resolve())
# Add fastmcp run command
⋮----
server_config: dict[str, Any] = {"command": "uv", "args": args}
# Add environment variables if specified

================
File: src/fastmcp/cli/cli.py
================
"""FastMCP CLI tools."""
⋮----
logger = get_logger("cli")
console = Console()
app = typer.Typer(
⋮----
no_args_is_help=True,  # Show help if no args provided
⋮----
def _get_npx_command()
⋮----
"""Get the correct npx command for the current platform."""
⋮----
# Try both npx.cmd and npx.exe on Windows
⋮----
return "npx"  # On Unix-like systems, just use npx
def _parse_env_var(env_var: str) -> tuple[str, str]
⋮----
"""Parse environment variable string in format KEY=VALUE."""
⋮----
"""Build the uv run command that runs a MCP server through mcp run."""
cmd = ["uv"]
⋮----
# Add mcp run command
⋮----
@app.command()
def version(ctx: Context)
⋮----
info = {
g = Table.grid(padding=(0, 1))
⋮----
"""Run a MCP server with the MCP Inspector."""
⋮----
# Import server to get dependencies
server: FastMCP = run_module.import_server(file, server_object)
⋮----
with_packages = list(set(with_packages + server.dependencies))
env_vars = {}
⋮----
# Get the correct npx command
npx_cmd = _get_npx_command()
⋮----
inspector_cmd = "@modelcontextprotocol/inspector"
⋮----
uv_cmd = _build_uv_command(server_spec, with_editable, with_packages)
# Run the MCP Inspector command with shell=True on Windows
shell = sys.platform == "win32"
process = subprocess.run(
⋮----
"""Run a MCP server or connect to a remote one.
    The server can be specified in three ways:
    1. Module approach: server.py - runs the module directly, looking for an object named mcp/server/app.\n
    2. Import approach: server.py:app - imports and runs the specified server object.\n
    3. URL approach: http://server-url - connects to a remote server and creates a proxy.\n\n
    Note: This command runs the server directly. You are responsible for ensuring
    all dependencies are available.
    Server arguments can be passed after -- :
    fastmcp run server.py -- --config config.json --debug
    """
server_args = ctx.args  # extra args after --
⋮----
"""Install a MCP server in the Claude desktop app.
    Environment variables are preserved once added and only updated if new values
    are explicitly provided.
    """
⋮----
# Try to import server to get its name, but fall back to file name if dependencies
# missing
name = server_name
server = None
⋮----
server = run_module.import_server(file, server_object)
name = server.name
⋮----
name = file.stem
# Get server dependencies if available
server_dependencies = getattr(server, "dependencies", []) if server else []
⋮----
with_packages = list(set(with_packages + server_dependencies))
# Process environment variables if provided
env_dict: dict[str, str] | None = None
⋮----
env_dict = {}
# Load from .env file if specified
⋮----
# Add command line environment variables
⋮----
"""Inspect a FastMCP server and generate a JSON report.
    This command analyzes a FastMCP server (v1.x or v2.x) and generates
    a comprehensive JSON report containing information about the server's
    name, instructions, version, tools, prompts, resources, templates,
    and capabilities.
    Examples:
        fastmcp inspect server.py
        fastmcp inspect server.py -o report.json
        fastmcp inspect server.py:mcp -o analysis.json
        fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
    """
# Parse the server specification
⋮----
# Import the server
⋮----
# Get server information
async def get_info()
⋮----
# Try to use existing event loop if available
⋮----
# If there's already a loop running, we need to run in a thread
⋮----
future = executor.submit(asyncio.run, get_info())
info = future.result()
⋮----
# No running loop, safe to use asyncio.run
info = asyncio.run(get_info())
info_json = TypeAdapter(FastMCPInfo).dump_json(info, indent=2)
# Ensure output directory exists
⋮----
# Write JSON report (always pretty-printed)
⋮----
# Print summary to console

================
File: src/fastmcp/cli/run.py
================
"""FastMCP run command implementation."""
⋮----
logger = get_logger("cli.run")
def is_url(path: str) -> bool
⋮----
"""Check if a string is a URL."""
url_pattern = re.compile(r"^https?://")
⋮----
def parse_file_path(server_spec: str) -> tuple[Path, str | None]
⋮----
"""Parse a file path that may include a server object specification.
    Args:
        server_spec: Path to file, optionally with :object suffix
    Returns:
        Tuple of (file_path, server_object)
    """
# First check if we have a Windows path (e.g., C:\...)
has_windows_drive = len(server_spec) > 1 and server_spec[1] == ":"
# Split on the last colon, but only if it's not part of the Windows drive letter
# and there's actually another colon in the string after the drive letter
⋮----
# Resolve the file path
file_path = Path(file_str).expanduser().resolve()
⋮----
def import_server(file: Path, server_object: str | None = None) -> Any
⋮----
"""Import a MCP server from a file.
    Args:
        file: Path to the file
        server_object: Optional object name in format "module:object" or just "object"
    Returns:
        The server object
    """
# Add parent directory to Python path so imports can be resolved
file_dir = str(file.parent)
⋮----
# Import the module
spec = importlib.util.spec_from_file_location("server_module", file)
⋮----
module = importlib.util.module_from_spec(spec)
⋮----
# If no object specified, try common server names
⋮----
# Look for the most common server object names
⋮----
# Handle module:object syntax
⋮----
server_module = importlib.import_module(module_name)
server = getattr(server_module, object_name, None)
⋮----
# Just object name
server = getattr(module, server_object, None)
⋮----
def create_client_server(url: str) -> Any
⋮----
"""Create a FastMCP server from a client URL.
    Args:
        url: The URL to connect to
    Returns:
        A FastMCP server instance
    """
⋮----
client = fastmcp.Client(url)
server = fastmcp.FastMCP.from_client(client)
⋮----
"""Import a server with optional command line arguments.
    Args:
        file: Path to the server file
        server_object: Optional server object name
        server_args: Optional command line arguments to inject
    Returns:
        The imported server object
    """
⋮----
original_argv = sys.argv[:]
⋮----
"""Run a MCP server or connect to a remote one.
    Args:
        server_spec: Python file, object specification (file:obj), or URL
        transport: Transport protocol to use
        host: Host to bind to when using http transport
        port: Port to bind to when using http transport
        log_level: Log level
        server_args: Additional arguments to pass to the server
    """
⋮----
# Handle URL case
server = create_client_server(server_spec)
⋮----
# Handle file case
⋮----
server = import_server_with_args(file, server_object, server_args)
⋮----
# Run the server
kwargs = {}

================
File: src/fastmcp/client/auth/__init__.py
================
__all__ = ["BearerAuth", "OAuth"]

================
File: src/fastmcp/client/auth/bearer.py
================
__all__ = ["BearerAuth"]
logger = get_logger(__name__)
class BearerAuth(httpx.Auth)
⋮----
def __init__(self, token: str)
def auth_flow(self, request)

================
File: src/fastmcp/client/auth/oauth.py
================
__all__ = ["OAuth"]
logger = get_logger(__name__)
def default_cache_dir() -> Path
class FileTokenStorage(TokenStorage)
⋮----
"""
    File-based token storage implementation for OAuth credentials and tokens.
    Implements the mcp.client.auth.TokenStorage protocol.
    Each instance is tied to a specific server URL for proper token isolation.
    """
def __init__(self, server_url: str, cache_dir: Path | None = None)
⋮----
"""Initialize storage for a specific server URL."""
⋮----
@staticmethod
    def get_base_url(url: str) -> str
⋮----
"""Extract the base URL (scheme + host) from a URL."""
parsed = urlparse(url)
⋮----
def get_cache_key(self) -> str
⋮----
"""Generate a safe filesystem key from the server's base URL."""
base_url = self.get_base_url(self.server_url)
⋮----
def _get_file_path(self, file_type: Literal["client_info", "tokens"]) -> Path
⋮----
"""Get the file path for the specified cache file type."""
key = self.get_cache_key()
⋮----
async def get_tokens(self) -> OAuthToken | None
⋮----
"""Load tokens from file storage."""
path = self._get_file_path("tokens")
⋮----
tokens = OAuthToken.model_validate_json(path.read_text())
# now = datetime.datetime.now(datetime.timezone.utc)
# if tokens.expires_at is not None and tokens.expires_at <= now:
#     logger.debug(f"Token expired for {self.get_base_url(self.server_url)}")
#     return None
⋮----
async def set_tokens(self, tokens: OAuthToken) -> None
⋮----
"""Save tokens to file storage."""
⋮----
async def get_client_info(self) -> OAuthClientInformationFull | None
⋮----
"""Load client information from file storage."""
path = self._get_file_path("client_info")
⋮----
client_info = OAuthClientInformationFull.model_validate_json(
# Check if we have corresponding valid tokens
# If no tokens exist, the OAuth flow was incomplete and we should
# force a fresh client registration
tokens = await self.get_tokens()
⋮----
# Clear the incomplete client info
client_info_path = self._get_file_path("client_info")
⋮----
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None
⋮----
"""Save client information to file storage."""
⋮----
def clear(self) -> None
⋮----
"""Clear all cached data for this server."""
file_types: list[Literal["client_info", "tokens"]] = ["client_info", "tokens"]
⋮----
path = self._get_file_path(file_type)
⋮----
@classmethod
    def clear_all(cls, cache_dir: Path | None = None) -> None
⋮----
"""Clear all cached data for all servers."""
cache_dir = cache_dir or default_cache_dir()
⋮----
"""
    Discover OAuth metadata from the server using RFC 8414 well-known endpoint.
    Args:
        server_base_url: Base URL of the OAuth server (e.g., "https://example.com")
        httpx_kwargs: Additional kwargs for httpx client
    Returns:
        OAuth metadata if found, None otherwise
    """
well_known_url = urljoin(server_base_url, "/.well-known/oauth-authorization-server")
⋮----
response = await client.get(well_known_url, timeout=10.0)
⋮----
"""
    Check if the MCP endpoint requires authentication by making a test request.
    Returns:
        True if auth appears to be required, False otherwise
    """
⋮----
# Try a simple request to the endpoint
response = await client.get(mcp_url, timeout=5.0)
# If we get 401/403, auth is likely required
⋮----
# Check for WWW-Authenticate header
⋮----
# If we get a successful response, auth may not be required
⋮----
# If we can't connect, assume auth might be required
⋮----
"""
    Create an OAuthClientProvider for an MCP server.
    This is intended to be provided to the `auth` parameter of an
    httpx.AsyncClient (or appropriate FastMCP client/transport instance)
    Args:
        mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/")
        scopes: OAuth scopes to request. Can be a
        space-separated string or a list of strings.
        client_name: Name for this client during registration
        token_storage_cache_dir: Directory for FileTokenStorage
        additional_client_metadata: Extra fields for OAuthClientMetadata
    Returns:
        OAuthClientProvider
    """
parsed_url = urlparse(mcp_url)
server_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
# Setup OAuth client
redirect_port = find_available_port()
redirect_uri = f"http://127.0.0.1:{redirect_port}/callback"
⋮----
scopes = " ".join(scopes)
client_metadata = OAuthClientMetadata(
# Create server-specific token storage
storage = FileTokenStorage(
# Define OAuth handlers
async def redirect_handler(authorization_url: str) -> None
⋮----
"""Open browser for authorization."""
⋮----
async def callback_handler() -> tuple[str, str | None]
⋮----
"""Handle OAuth callback and return (auth_code, state)."""
# Create a future to capture the OAuth response
response_future = asyncio.get_running_loop().create_future()
# Create server with the future
server = create_oauth_callback_server(
# Run server until response is received with timeout logic
⋮----
TIMEOUT = 300.0  # 5 minute timeout
⋮----
await asyncio.sleep(0.1)  # Allow server to shutdown gracefully
⋮----
# Create OAuth provider
oauth_provider = OAuthClientProvider(

================
File: src/fastmcp/client/__init__.py
================
__all__ = [

================
File: src/fastmcp/client/client.py
================
__all__ = [
logger = get_logger(__name__)
class Client(Generic[ClientTransportT])
⋮----
"""
    MCP client that delegates connection management to a Transport instance.
    The Client class is responsible for MCP protocol logic, while the Transport
    handles connection establishment and management. Client provides methods for
    working with resources, prompts, tools and other MCP capabilities.
    Args:
        transport: Connection source specification, which can be:
            - ClientTransport: Direct transport instance
            - FastMCP: In-process FastMCP server
            - AnyUrl | str: URL to connect to
            - Path: File path for local socket
            - MCPConfig: MCP server configuration
            - dict: Transport configuration
        roots: Optional RootsList or RootsHandler for filesystem access
        sampling_handler: Optional handler for sampling requests
        log_handler: Optional handler for log messages
        message_handler: Optional handler for protocol messages
        progress_handler: Optional handler for progress notifications
        timeout: Optional timeout for requests (seconds or timedelta)
        init_timeout: Optional timeout for initial connection (seconds or timedelta).
            Set to 0 to disable. If None, uses the value in the FastMCP global settings.
    Examples:
        ```python # Connect to FastMCP server client =
        Client("http://localhost:8080")
        async with client:
            # List available resources resources = await client.list_resources()
            # Call a tool result = await client.call_tool("my_tool", {"param":
            "value"})
        ```
    """
⋮----
def __new__(cls, transport, **kwargs) -> Client
⋮----
instance = super().__new__(cls)
⋮----
# Common args
⋮----
log_handler = default_log_handler
⋮----
progress_handler = default_progress_handler
⋮----
timeout = datetime.timedelta(seconds=timeout)
# handle init handshake timeout
⋮----
init_timeout = fastmcp.settings.client_init_timeout
⋮----
init_timeout = init_timeout.total_seconds()
⋮----
init_timeout = None
⋮----
init_timeout = float(init_timeout)
⋮----
# session context management
⋮----
@property
    def session(self) -> ClientSession
⋮----
"""Get the current active session. Raises RuntimeError if not connected."""
⋮----
@property
    def initialize_result(self) -> mcp.types.InitializeResult
⋮----
"""Get the result of the initialization request."""
⋮----
def set_roots(self, roots: RootsList | RootsHandler) -> None
⋮----
"""Set the roots for the client. This does not automatically call `send_roots_list_changed`."""
⋮----
def set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
⋮----
"""Set the sampling callback for the client."""
⋮----
"""Set the elicitation callback for the client."""
⋮----
def is_connected(self) -> bool
⋮----
"""Check if the client is currently connected."""
⋮----
@asynccontextmanager
    async def _context_manager(self)
⋮----
# Initialize the session
⋮----
async def __aenter__(self)
⋮----
# Check if session task failed and raise error immediately
⋮----
exception = self._session_task.exception()
⋮----
async def __aexit__(self, exc_type, exc_val, exc_tb)
async def _connect(self)
⋮----
# ensure only one session is running at a time to avoid race conditions
⋮----
need_to_start = self._session_task is None or self._session_task.done()
⋮----
async def _disconnect(self, force: bool = False)
⋮----
# if we are forcing a disconnect, reset the nesting counter
⋮----
# otherwise decrement to check if we are done nesting
⋮----
# if we are still nested, return
⋮----
# stop the active seesion
⋮----
runner_task = self._session_task
⋮----
# wait for the session to finish
⋮----
# Reset for future reconnects
⋮----
async def _session_runner(self)
⋮----
# Session/context is now ready
⋮----
# Wait until disconnect/stop is requested
⋮----
# On exit, ensure ready event is set (idempotent)
⋮----
# Ensure ready event is set even if context manager entry fails
⋮----
async def close(self)
# --- MCP Client Methods ---
async def ping(self) -> bool
⋮----
"""Send a ping request."""
result = await self.session.send_ping()
⋮----
"""Send a cancellation notification for an in-progress request."""
notification = mcp.types.ClientNotification(
⋮----
"""Send a progress notification."""
⋮----
async def set_logging_level(self, level: mcp.types.LoggingLevel) -> None
⋮----
"""Send a logging/setLevel request."""
⋮----
async def send_roots_list_changed(self) -> None
⋮----
"""Send a roots/list_changed notification."""
⋮----
# --- Resources ---
async def list_resources_mcp(self) -> mcp.types.ListResourcesResult
⋮----
"""Send a resources/list request and return the complete MCP protocol result.
        Returns:
            mcp.types.ListResourcesResult: The complete response object from the protocol,
                containing the list of resources and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.session.list_resources()
⋮----
async def list_resources(self) -> list[mcp.types.Resource]
⋮----
"""Retrieve a list of resources available on the server.
        Returns:
            list[mcp.types.Resource]: A list of Resource objects.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.list_resources_mcp()
⋮----
"""Send a resources/listResourceTemplates request and return the complete MCP protocol result.
        Returns:
            mcp.types.ListResourceTemplatesResult: The complete response object from the protocol,
                containing the list of resource templates and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.session.list_resource_templates()
⋮----
"""Retrieve a list of resource templates available on the server.
        Returns:
            list[mcp.types.ResourceTemplate]: A list of ResourceTemplate objects.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.list_resource_templates_mcp()
⋮----
"""Send a resources/read request and return the complete MCP protocol result.
        Args:
            uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
        Returns:
            mcp.types.ReadResourceResult: The complete response object from the protocol,
                containing the resource contents and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
⋮----
uri = AnyUrl(uri)  # Ensure AnyUrl
result = await self.session.read_resource(uri)
⋮----
"""Read the contents of a resource or resolved template.
        Args:
            uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
        Returns:
            list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]: A list of content
                objects, typically containing either text or binary data.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
⋮----
uri = AnyUrl(uri)  # Ensure AnyUrl
⋮----
result = await self.read_resource_mcp(uri)
⋮----
# async def subscribe_resource(self, uri: AnyUrl | str) -> None:
#     """Send a resources/subscribe request."""
#     if isinstance(uri, str):
#         uri = AnyUrl(uri)
#     await self.session.subscribe_resource(uri)
# async def unsubscribe_resource(self, uri: AnyUrl | str) -> None:
#     """Send a resources/unsubscribe request."""
⋮----
#     await self.session.unsubscribe_resource(uri)
# --- Prompts ---
async def list_prompts_mcp(self) -> mcp.types.ListPromptsResult
⋮----
"""Send a prompts/list request and return the complete MCP protocol result.
        Returns:
            mcp.types.ListPromptsResult: The complete response object from the protocol,
                containing the list of prompts and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.session.list_prompts()
⋮----
async def list_prompts(self) -> list[mcp.types.Prompt]
⋮----
"""Retrieve a list of prompts available on the server.
        Returns:
            list[mcp.types.Prompt]: A list of Prompt objects.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.list_prompts_mcp()
⋮----
# --- Prompt ---
⋮----
"""Send a prompts/get request and return the complete MCP protocol result.
        Args:
            name (str): The name of the prompt to retrieve.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
        Returns:
            mcp.types.GetPromptResult: The complete response object from the protocol,
                containing the prompt messages and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
# Serialize arguments for MCP protocol - convert non-string values to JSON
serialized_arguments: dict[str, str] | None = None
⋮----
serialized_arguments = {}
⋮----
# Use pydantic_core.to_json for consistent serialization
⋮----
result = await self.session.get_prompt(
⋮----
"""Retrieve a rendered prompt message list from the server.
        Args:
            name (str): The name of the prompt to retrieve.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
        Returns:
            mcp.types.GetPromptResult: The complete response object from the protocol,
                containing the prompt messages and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.get_prompt_mcp(name=name, arguments=arguments)
⋮----
# --- Completion ---
⋮----
"""Send a completion request and return the complete MCP protocol result.
        Args:
            ref (mcp.types.ResourceReference | mcp.types.PromptReference): The reference to complete.
            argument (dict[str, str]): Arguments to pass to the completion request.
        Returns:
            mcp.types.CompleteResult: The complete response object from the protocol,
                containing the completion and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.session.complete(ref=ref, argument=argument)
⋮----
"""Send a completion request to the server.
        Args:
            ref (mcp.types.ResourceReference | mcp.types.PromptReference): The reference to complete.
            argument (dict[str, str]): Arguments to pass to the completion request.
        Returns:
            mcp.types.Completion: The completion object.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.complete_mcp(ref=ref, argument=argument)
⋮----
# --- Tools ---
async def list_tools_mcp(self) -> mcp.types.ListToolsResult
⋮----
"""Send a tools/list request and return the complete MCP protocol result.
        Returns:
            mcp.types.ListToolsResult: The complete response object from the protocol,
                containing the list of tools and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.session.list_tools()
⋮----
async def list_tools(self) -> list[mcp.types.Tool]
⋮----
"""Retrieve a list of tools available on the server.
        Returns:
            list[mcp.types.Tool]: A list of Tool objects.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
result = await self.list_tools_mcp()
⋮----
# --- Call Tool ---
⋮----
"""Send a tools/call request and return the complete MCP protocol result.
        This method returns the raw CallToolResult object, which includes an isError flag
        and other metadata. It does not raise an exception if the tool call results in an error.
        Args:
            name (str): The name of the tool to call.
            arguments (dict[str, Any]): Arguments to pass to the tool.
            timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
            progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
        Returns:
            mcp.types.CallToolResult: The complete response object from the protocol,
                containing the tool result and any additional metadata.
        Raises:
            RuntimeError: If called while the client is not connected.
        """
⋮----
result = await self.session.call_tool(
⋮----
"""Call a tool on the server.
        Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
        Args:
            name (str): The name of the tool to call.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
            timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
            progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
        Returns:
            CallToolResult:
                The content returned by the tool. If the tool returns structured
                outputs, they are returned as a dataclass (if an output schema
                is available) or a dictionary; otherwise, a list of content
                blocks is returned. Note: to receive both structured and
                unstructured outputs, use call_tool_mcp instead and access the
                raw result object.
        Raises:
            ToolError: If the tool call results in an error.
            RuntimeError: If called while the client is not connected.
        """
result = await self.call_tool_mcp(
data = None
⋮----
msg = cast(mcp.types.TextContent, result.content[0]).text
⋮----
output_schema = self.session._tool_output_schemas.get(name)
⋮----
output_schema = output_schema.get("properties", {}).get(
structured_content = result.structuredContent.get("result")
⋮----
structured_content = result.structuredContent
output_type = json_schema_to_type(output_schema)
type_adapter = get_cached_typeadapter(output_type)
data = type_adapter.validate_python(structured_content)
⋮----
data = result.structuredContent
⋮----
@dataclass
class CallToolResult
⋮----
content: list[mcp.types.ContentBlock]
structured_content: dict[str, Any] | None
data: Any = None
is_error: bool = False

================
File: src/fastmcp/client/elicitation.py
================
__all__ = ["ElicitRequestParams", "ElicitResult", "ElicitationHandler"]
T = TypeVar("T")
class ElicitResult(MCPElicitResult, Generic[T])
⋮----
content: T | None = None
ElicitationHandler: TypeAlias = Callable[
⋮----
str,  # message
type[T],  # a class for creating a structured response
⋮----
response_type = json_schema_to_type(params.requestedSchema)
result = await elicitation_handler(
# if the user returns data, we assume they've accepted the elicitation
⋮----
result = ElicitResult(action="accept", content=result)
content = to_jsonable_python(result.content)

================
File: src/fastmcp/client/logging.py
================
logger = get_logger(__name__)
LogMessage: TypeAlias = LoggingMessageNotificationParams
LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
async def default_log_handler(message: LogMessage) -> None
def create_log_callback(handler: LogHandler | None = None) -> LoggingFnT
⋮----
handler = default_log_handler
async def log_callback(params: LoggingMessageNotificationParams) -> None

================
File: src/fastmcp/client/messages.py
================
Message: TypeAlias = (
MessageHandlerT: TypeAlias = MessageHandlerFnT
class MessageHandler
⋮----
"""
    This class is used to handle MCP messages sent to the client. It is used to handle all messages,
    requests, notifications, and exceptions. Users can override any of the hooks
    """
⋮----
async def dispatch(self, message: Message) -> None
⋮----
# handle all messages
⋮----
# requests
⋮----
# handle all requests
⋮----
# handle specific requests
⋮----
# notifications
⋮----
# handle all notifications
⋮----
# handle specific notifications
⋮----
async def on_message(self, message: Message) -> None
⋮----
async def on_ping(self, message: mcp.types.PingRequest) -> None
async def on_list_roots(self, message: mcp.types.ListRootsRequest) -> None
async def on_create_message(self, message: mcp.types.CreateMessageRequest) -> None
async def on_notification(self, message: mcp.types.ServerNotification) -> None
async def on_exception(self, message: Exception) -> None
async def on_progress(self, message: mcp.types.ProgressNotification) -> None
⋮----
async def on_cancelled(self, message: mcp.types.CancelledNotification) -> None

================
File: src/fastmcp/client/oauth_callback.py
================
"""
OAuth callback server for handling authorization code flows.
This module provides a reusable callback server that can handle OAuth redirects
and display styled responses to users.
"""
⋮----
logger = get_logger(__name__)
⋮----
"""Create a styled HTML response for OAuth callbacks."""
status_emoji = "✅" if is_success else "❌"
status_color = "#10b981" if is_success else "#ef4444"  # emerald-500 / red-500
# Add server info for success cases
server_info = ""
⋮----
server_info = f"""
⋮----
@dataclass
class CallbackResponse
⋮----
code: str | None = None
state: str | None = None
error: str | None = None
error_description: str | None = None
⋮----
@classmethod
    def from_dict(cls, data: dict[str, str]) -> CallbackResponse
def to_dict(self) -> dict[str, str]
⋮----
"""
    Create an OAuth callback server.
    Args:
        port: The port to run the server on
        callback_path: The path to listen for OAuth redirects on
        server_url: Optional server URL to display in success messages
        response_future: Optional future to resolve when OAuth callback is received
    Returns:
        Configured uvicorn Server instance (not yet running)
    """
async def callback_handler(request: Request)
⋮----
"""Handle OAuth callback requests with proper HTML responses."""
query_params = dict(request.query_params)
callback_response = CallbackResponse.from_dict(query_params)
⋮----
error_desc = callback_response.error_description or "Unknown error"
# Resolve future with exception if provided
⋮----
# Success case
⋮----
app = Starlette(routes=[Route(callback_path, callback_handler)])
⋮----
"""Run a test server when executed directly."""
⋮----
port = find_available_port()
⋮----
# Create test server without future (just for testing HTML responses)
server = create_oauth_callback_server(
# Open browser to success example
⋮----
# Run with uvicorn directly

================
File: src/fastmcp/client/progress.py
================
logger = get_logger(__name__)
ProgressHandler: TypeAlias = ProgressFnT
⋮----
"""Default handler for progress notifications.
    Logs progress updates at debug level, properly handling missing total or message values.
    Args:
        progress: Current progress value
        total: Optional total expected value
        message: Optional status message
    """
⋮----
# We have both progress and total
percent = (progress / total) * 100
progress_str = f"{progress}/{total} ({percent:.1f}%)"
⋮----
# We only have progress
progress_str = f"{progress}"
# Include message if available
⋮----
log_msg = f"Progress: {progress_str} - {message}"
⋮----
log_msg = f"Progress: {progress_str}"

================
File: src/fastmcp/client/roots.py
================
RootsList: TypeAlias = list[str] | list[mcp.types.Root] | list[str | mcp.types.Root]
RootsHandler: TypeAlias = (
def convert_roots_list(roots: RootsList) -> list[mcp.types.Root]
⋮----
roots_list = []
⋮----
roots = convert_roots_list(roots)
⋮----
roots = fn(context)
⋮----
roots = await roots

================
File: src/fastmcp/client/sampling.py
================
__all__ = ["SamplingMessage", "SamplingParams", "SamplingHandler"]
SamplingHandler: TypeAlias = Callable[
def create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT
⋮----
result = sampling_handler(params.messages, params, context)
⋮----
result = await result
⋮----
result = CreateMessageResult(

================
File: src/fastmcp/client/transports.py
================
logger = get_logger(__name__)
# TypeVar for preserving specific ClientTransport subclass types
ClientTransportT = TypeVar("ClientTransportT", bound="ClientTransport")
__all__ = [
class SessionKwargs(TypedDict, total=False)
⋮----
"""Keyword arguments for the MCP ClientSession constructor."""
read_timeout_seconds: datetime.timedelta | None
sampling_callback: SamplingFnT | None
list_roots_callback: ListRootsFnT | None
logging_callback: LoggingFnT | None
elicitation_callback: ElicitationFnT | None
message_handler: MessageHandlerFnT | None
client_info: mcp.types.Implementation | None
class ClientTransport(abc.ABC)
⋮----
"""
    Abstract base class for different MCP client transport mechanisms.
    A Transport is responsible for establishing and managing connections
    to an MCP server, and providing a ClientSession within an async context.
    """
⋮----
"""
        Establishes a connection and yields an active ClientSession.
        The ClientSession is *not* expected to be initialized in this context manager.
        The session is guaranteed to be valid only within the scope of the
        async context manager. Connection setup and teardown are handled
        within this context.
        Args:
            **session_kwargs: Keyword arguments to pass to the ClientSession
                              constructor (e.g., callbacks, timeouts).
        Yields:
            A mcp.ClientSession instance.
        """
⋮----
yield  # type: ignore
def __repr__(self) -> str
⋮----
# Basic representation for subclasses
⋮----
async def close(self)
⋮----
"""Close the transport."""
⋮----
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None)
class WSTransport(ClientTransport)
⋮----
"""Transport implementation that connects to an MCP server via WebSockets."""
def __init__(self, url: str | AnyUrl)
⋮----
# we never really used this transport, so it can be removed at any time
⋮----
url = str(url)
⋮----
class SSETransport(ClientTransport)
⋮----
"""Transport implementation that connects to an MCP server via Server-Sent Events."""
⋮----
# Don't modify the URL path - respect the exact URL provided by the user
# Some servers are strict about trailing slashes (e.g., PayPal MCP)
⋮----
sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout)
⋮----
auth = OAuth(self.url)
⋮----
auth = BearerAuth(auth)
⋮----
client_kwargs: dict[str, Any] = {}
# load headers from an active HTTP request, if available. This will only be true
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
# need to be forwarded to the remote server.
⋮----
# sse_read_timeout has a default value set, so we can't pass None without overriding it
# instead we simply leave the kwarg out if it's not provided
⋮----
read_timeout_seconds = cast(
⋮----
class StreamableHttpTransport(ClientTransport)
⋮----
"""Transport implementation that connects to an MCP server via Streamable HTTP Requests."""
⋮----
class StdioTransport(ClientTransport)
⋮----
"""
    Base transport for connecting to an MCP server via subprocess with stdio.
    This is a base class that can be subclassed for specific command-based
    transports like Python, Node, Uvx, etc.
    """
⋮----
"""
        Initialize a Stdio transport.
        Args:
            command: The command to run (e.g., "python", "node", "uvx")
            args: The arguments to pass to the command
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
⋮----
keep_alive = True
⋮----
async def _connect_task()
⋮----
server_params = StdioServerParameters(
transport = await stack.enter_async_context(
⋮----
# Wait until disconnect is requested (stop_event is set)
⋮----
# Clean up client on exit
⋮----
# Ensure ready event is set even if connection fails
⋮----
# start the connection task
⋮----
# wait for the client to be ready before returning
⋮----
# Check if connect task completed with an exception (early failure)
⋮----
exception = self._connect_task.exception()
⋮----
async def disconnect(self)
⋮----
# signal the connection task to stop
⋮----
# wait for the connection task to finish cleanly
⋮----
# reset variables and events for potential future reconnects
⋮----
class PythonStdioTransport(StdioTransport)
⋮----
"""Transport for running Python scripts."""
⋮----
"""
        Initialize a Python transport.
        Args:
            script_path: Path to the Python script to run
            args: Additional arguments to pass to the script
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            python_cmd: Python command to use (default: "python")
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
script_path = Path(script_path).resolve()
⋮----
full_args = [str(script_path)]
⋮----
class FastMCPStdioTransport(StdioTransport)
⋮----
"""Transport for running FastMCP servers using the FastMCP CLI."""
⋮----
class NodeStdioTransport(StdioTransport)
⋮----
"""Transport for running Node.js scripts."""
⋮----
"""
        Initialize a Node transport.
        Args:
            script_path: Path to the Node.js script to run
            args: Additional arguments to pass to the script
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            node_cmd: Node.js command to use (default: "node")
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
⋮----
class UvxStdioTransport(StdioTransport)
⋮----
"""Transport for running commands via the uvx tool."""
⋮----
"""
        Initialize a Uvx transport.
        Args:
            tool_name: Name of the tool to run via uvx
            tool_args: Arguments to pass to the tool
            project_directory: Project directory (for package resolution)
            python_version: Python version to use
            with_packages: Additional packages to include
            from_package: Package to install the tool from
            env_vars: Additional environment variables
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
# Basic validation
⋮----
# Build uvx arguments
uvx_args = []
⋮----
# Add the tool name and tool args
⋮----
# Get environment with any additional variables
env = None
⋮----
env = os.environ.copy()
⋮----
class NpxStdioTransport(StdioTransport)
⋮----
"""Transport for running commands via the npx tool."""
⋮----
"""
        Initialize an Npx transport.
        Args:
            package: Name of the npm package to run
            args: Arguments to pass to the package command
            project_directory: Project directory with package.json
            env_vars: Additional environment variables
            use_package_lock: Whether to use package-lock.json (--prefer-offline)
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
# verify npx is installed
⋮----
# Build npx arguments
npx_args = []
⋮----
# Add the package name and args
⋮----
class FastMCPTransport(ClientTransport)
⋮----
"""In-memory transport for FastMCP servers.
    This transport connects directly to a FastMCP server instance in the same
    Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
    servers from the low-level MCP SDK. This is particularly useful for unit
    tests or scenarios where client and server run in the same runtime.
    """
def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False)
⋮----
"""Initialize a FastMCPTransport from a FastMCP server instance."""
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
# ``_mcp_server`` attribute pointing to the underlying MCP server
# implementation, so we can treat them identically.
⋮----
# Create a cancel scope for the server task
⋮----
class MCPConfigTransport(ClientTransport)
⋮----
"""Transport for connecting to one or more MCP servers defined in an MCPConfig.
    This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
    object or dictionary matching the MCPConfig schema. It supports two key scenarios:
    1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
    2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
       all servers on a single FastMCP instance, with each server's name used as its mounting prefix.
    In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
    and resources with the pattern `protocol://{server_name}/path/to/resource`.
    This is particularly useful for creating clients that need to interact with multiple specialized
    MCP servers through a single interface, simplifying client code.
    Examples:
        ```python
        from fastmcp import Client
        from fastmcp.utilities.mcp_config import MCPConfig
        # Create a config with multiple servers
        config = {
            "mcpServers": {
                "weather": {
                    "url": "https://weather-api.example.com/mcp",
                    "transport": "http"
                },
                "calendar": {
                    "url": "https://calendar-api.example.com/mcp",
                    "transport": "http"
                }
            }
        }
        # Create a client with the config
        client = Client(config)
        async with client:
            # Access tools with prefixes
            weather = await client.call_tool("weather_get_forecast", {"city": "London"})
            events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})
            # Access resources with prefixed URIs
            icons = await client.read_resource("weather://weather/icons/sunny")
        ```
    """
def __init__(self, config: MCPConfig | dict)
⋮----
config = MCPConfig.from_dict(config)
⋮----
# if there are no servers, raise an error
⋮----
# if there's exactly one server, create a client for that server
⋮----
# otherwise create a composite client
⋮----
composite_server = FastMCP()
⋮----
server_client = Client(transport=server.to_transport())
⋮----
@overload
def infer_transport(transport: ClientTransportT) -> ClientTransportT: ...
⋮----
@overload
def infer_transport(transport: FastMCP) -> FastMCPTransport: ...
⋮----
@overload
def infer_transport(transport: FastMCP1Server) -> FastMCPTransport: ...
⋮----
@overload
def infer_transport(transport: MCPConfig) -> MCPConfigTransport: ...
⋮----
@overload
def infer_transport(transport: dict[str, Any]) -> MCPConfigTransport: ...
⋮----
@overload
def infer_transport(transport: Path) -> PythonStdioTransport | NodeStdioTransport: ...
⋮----
"""
    Infer the appropriate transport type from the given transport argument.
    This function attempts to infer the correct transport type from the provided
    argument, handling various input types and converting them to the appropriate
    ClientTransport subclass.
    The function supports these input types:
    - ClientTransport: Used directly without modification
    - FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
    - Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
    - AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
    - MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
    For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.
    For MCPConfig with multiple servers, a composite client is created where each server
    is mounted with its name as prefix. This allows accessing tools and resources from multiple
    servers through a single unified client interface, using naming patterns like
    `servername_toolname` for tools and `protocol://servername/path` for resources.
    If the MCPConfig contains only one server, a direct connection is established without prefixing.
    Examples:
        ```python
        # Connect to a local Python script
        transport = infer_transport("my_script.py")
        # Connect to a remote server via HTTP
        transport = infer_transport("http://example.com/mcp")
        # Connect to multiple servers using MCPConfig
        config = {
            "mcpServers": {
                "weather": {"url": "http://weather.example.com/mcp"},
                "calendar": {"url": "http://calendar.example.com/mcp"}
            }
        }
        transport = infer_transport(config)
        ```
    """
# the transport is already a ClientTransport
⋮----
# the transport is a FastMCP server (2.x or 1.0)
⋮----
inferred_transport = FastMCPTransport(mcp=transport)
# the transport is a path to a script
⋮----
inferred_transport = PythonStdioTransport(script_path=transport)
⋮----
inferred_transport = NodeStdioTransport(script_path=transport)
⋮----
# the transport is an http(s) URL
⋮----
inferred_transport_type = infer_transport_type_from_url(transport)
⋮----
inferred_transport = SSETransport(url=transport)
⋮----
inferred_transport = StreamableHttpTransport(url=transport)
# if the transport is a config dict or MCPConfig
⋮----
inferred_transport = MCPConfigTransport(config=transport)
# the transport is an unknown type

================
File: src/fastmcp/contrib/bulk_tool_caller/__init__.py
================
__all__ = ["BulkToolCaller"]

================
File: src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py
================
class CallToolRequest(BaseModel)
⋮----
"""A class to represent a request to call a tool with specific arguments."""
tool: str = Field(description="The name of the tool to call.")
arguments: dict[str, Any] = Field(
class CallToolRequestResult(CallToolResult)
⋮----
"""
    A class to represent the result of a bulk tool call.
    It extends CallToolResult to include information about the requested tool call.
    """
tool: str = Field(description="The name of the tool that was called.")
⋮----
"""
        Create a CallToolRequestResult from a CallToolResult.
        """
⋮----
class BulkToolCaller(MCPMixin)
⋮----
"""
    A class to provide a "bulk tool call" tool for a FastMCP server
    """
⋮----
"""
        Register the tools provided by this class with the given MCP server.
        """
⋮----
"""
        Call multiple tools registered on this MCP server in a single request. Each call can
         be for a different tool and can include different arguments. Useful for speeding up
         what would otherwise take several individual tool calls.
        """
results = []
⋮----
result = await self._call_tool(tool_call.tool, tool_call.arguments)
⋮----
"""
        Call a single tool registered on this MCP server multiple times with a single request.
         Each call can include different arguments. Useful for speeding up what would otherwise
         take several individual tool calls.
        Args:
            tool: The name of the tool to call.
            tool_arguments: A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
        """
⋮----
result = await self._call_tool(tool, tool_call_arguments)
⋮----
"""
        Helper method to call a tool with the provided arguments.
        """
⋮----
result = await client.call_tool_mcp(name=tool, arguments=arguments)

================
File: src/fastmcp/contrib/bulk_tool_caller/example.py
================
"""Sample code for FastMCP using MCPMixin."""
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
def echo_tool(text: str) -> str
⋮----
"""Echo the input text"""
⋮----
bulk_tool_caller = BulkToolCaller()

================
File: src/fastmcp/contrib/bulk_tool_caller/README.md
================
# Bulk Tool Caller

This module provides the `BulkToolCaller` class, which extends the `MCPMixin` to offer tools for performing multiple tool calls in a single request to a FastMCP server. This can be useful for optimizing interactions with the server by reducing the overhead of individual tool calls.

## Usage

To use the `BulkToolCaller`, see the example [example.py](./example.py) file. The `BulkToolCaller` can be instantiated and then registered with a FastMCP server URL. It provides methods to call multiple tools in bulk, either different tools or the same tool with different arguments.


## Provided Tools

The `BulkToolCaller` provides the following tools:

### `call_tools_bulk`

Calls multiple different tools registered on the MCP server in a single request.

- **Arguments:**
    - `tool_calls` (list of `CallToolRequest`): A list of objects, where each object specifies the `tool` name and `arguments` for an individual tool call.
    - `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.

- **Returns:**
    A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.

### `call_tool_bulk`

Calls a single tool registered on the MCP server multiple times with different arguments in a single request.

- **Arguments:**
    - `tool` (str): The name of the tool to call.
    - `tool_arguments` (list of dict): A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
    - `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.

- **Returns:**
    A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.

================
File: src/fastmcp/contrib/component_manager/__init__.py
================
__all__ = ["set_up_component_manager", "ComponentService"]

================
File: src/fastmcp/contrib/component_manager/component_manager.py
================
"""
Routes and helpers for managing tools, resources, and prompts in FastMCP.
Provides endpoints for enabling/disabling components via HTTP, with optional authentication scopes.
"""
⋮----
"""Set up routes for enabling/disabling tools, resources, and prompts.
    Args:
        server: The FastMCP server instance
        path: Path used to mount all component-related routes on the server
        required_scopes: Optional list of scopes required for these routes. Applies only if authentication is enabled.
    """
service = ComponentService(server)
routes: list[Route] = []
mounts: list[Mount] = []
route_configs = {
⋮----
def make_endpoint(action, component, config)
⋮----
"""
    Factory for creating Starlette endpoint functions for enabling/disabling a component.
    Args:
        action: 'enable' or 'disable'
        component: The component type (e.g., 'tool', 'resource', or 'prompt')
        config: Dict with param and handler functions for the component
    Returns:
        An async endpoint function for Starlette.
    """
async def endpoint(request: Request)
⋮----
name = request.path_params[config["param"].split(":")[0]]
⋮----
def make_route(action, component, config, required_scopes, root_path) -> Route
⋮----
"""
    Creates a Starlette Route for enabling/disabling a component.
    Args:
        action: 'enable' or 'disable'
        component: The component type
        config: Dict with param and handler functions
        required_scopes: Optional list of required auth scopes
        root_path: The base path for the route
    Returns:
        A Starlette Route object.
    """
endpoint = make_endpoint(action, component, config)
⋮----
path = f"/{{{config['param']}}}/{action}"
⋮----
path = f"{root_path}/{component}s/{{{config['param']}}}/{action}"
⋮----
path = f"/{component}s/{{{config['param']}}}/{action}"
⋮----
"""
    Build a list of Starlette Route objects for all components/actions.
    Args:
        route_configs: Dict describing component types and their handlers
        root_path: The base path for the routes
        required_scopes: Optional list of required auth scopes
    Returns:
        List of Starlette Route objects for component management.
    """
component_management_routes: list[Route] = []
⋮----
config: dict[str, Any] = route_configs[component]
⋮----
def build_component_manager_mount(route_configs, root_path, required_scopes) -> Mount
⋮----
"""
    Build a Starlette Mount with authentication for component management routes.
    Args:
        route_configs: Dict describing component types and their handlers
        root_path: The base path for the mount
        required_scopes: List of required auth scopes
    Returns:
        A Starlette Mount object with authentication middleware.
    """

================
File: src/fastmcp/contrib/component_manager/component_service.py
================
"""
ComponentService: Provides async management of tools, resources, and prompts for FastMCP servers.
Handles enabling/disabling components both locally and across mounted servers.
"""
⋮----
logger = get_logger(__name__)
class ComponentService
⋮----
"""Service for managing components like tools, resources, and prompts."""
def __init__(self, server: FastMCP)
async def _enable_tool(self, key: str) -> Tool
⋮----
"""Handle 'enableTool' requests.
        Args:
            key: The key of the tool to enable
        Returns:
            The tool that was enabled
        """
⋮----
# 1. Check local tools first. The server will have already applied its filter.
⋮----
tool: Tool = await self._server.get_tool(key)
⋮----
# 2. Check mounted servers using the filtered protocol path.
⋮----
tool_key = key.removeprefix(f"{mounted.prefix}_")
mounted_service = ComponentService(mounted.server)
tool = await mounted_service._enable_tool(tool_key)
⋮----
async def _disable_tool(self, key: str) -> Tool
⋮----
"""Handle 'disableTool' requests.
        Args:
            key: The key of the tool to disable
        Returns:
            The tool that was disabled
        """
⋮----
tool = await mounted_service._disable_tool(tool_key)
⋮----
async def _enable_resource(self, key: str) -> Resource | ResourceTemplate
⋮----
"""Handle 'enableResource' requests.
        Args:
            key: The key of the resource to enable
        Returns:
            The resource that was enabled
        """
⋮----
# 1. Check local resources first. The server will have already applied its filter.
⋮----
resource: Resource = await self._server.get_resource(key)
⋮----
template: ResourceTemplate = await self._server.get_resource_template(key)
⋮----
key = remove_resource_prefix(
⋮----
mounted_resource: (
⋮----
async def _disable_resource(self, key: str) -> Resource | ResourceTemplate
⋮----
"""Handle 'disableResource' requests.
        Args:
            key: The key of the resource to disable
        Returns:
            The resource that was disabled
        """
⋮----
async def _enable_prompt(self, key: str) -> Prompt
⋮----
"""Handle 'enablePrompt' requests.
        Args:
            key: The key of the prompt to enable
        Returns:
            The prompt that was enable
        """
⋮----
# 1. Check local prompts first. The server will have already applied its filter.
⋮----
prompt: Prompt = await self._server.get_prompt(key)
⋮----
prompt_key = key.removeprefix(f"{mounted.prefix}_")
⋮----
prompt = await mounted_service._enable_prompt(prompt_key)
⋮----
async def _disable_prompt(self, key: str) -> Prompt
⋮----
"""Handle 'disablePrompt' requests.
        Args:
            key: The key of the prompt to disable
        Returns:
            The prompt that was disabled
        """
⋮----
prompt = await mounted_service._disable_prompt(prompt_key)

================
File: src/fastmcp/contrib/component_manager/example.py
================
key_pair = RSAKeyPair.generate()
auth = BearerAuthProvider(
# Build main server
mcp_token = key_pair.create_token(
mcp = FastMCP(
# Set up main server component manager
⋮----
# Build mounted server
mounted_token = key_pair.create_token(
mounted = FastMCP(
# Set up mounted server component manager
⋮----
# Mount
⋮----
@mcp.resource("resource://greeting")
def get_greeting() -> str
⋮----
"""Provides a simple greeting message."""
⋮----
@mounted.tool("greeting")
def get_info() -> str
⋮----
"""Provides a simple info."""

================
File: src/fastmcp/contrib/component_manager/README.md
================
# Component Manager – Contrib Module for FastMCP

The **Component Manager** provides a unified API for enabling and disabling tools, resources, and prompts at runtime in a FastMCP server. This module is useful for dynamic control over which components are active, enabling advanced features like feature toggling, admin interfaces, or automation workflows.

---

## 🔧 Features

- Enable/disable **tools**, **resources**, and **prompts** via HTTP endpoints.
- Supports **local** and **mounted (server)** components.
- Customizable **API root path**.
- Optional **Auth scopes** for secured access.
- Fully integrates with FastMCP with minimal configuration.

---

## 📦 Installation

This module is part of the `fastmcp.contrib` package. No separate installation is required if you're already using **FastMCP**.

---

## 🚀 Usage

### Basic Setup

```python
from fastmcp import FastMCP
from fastmcp.contrib.component_manager import set_up_component_manager

mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.")
set_up_component_manager(server=mcp)
```

---

## 🔗 API Endpoints

All endpoints are registered at `/` by default, or under the custom path if one is provided.

### Tools

```http
POST /tools/{tool_name}/enable
POST /tools/{tool_name}/disable
```

### Resources

```http
POST /resources/{uri:path}/enable
POST /resources/{uri:path}/disable
```

 * Supports template URIs as well
```http
POST /resources/example://test/{id}/enable
POST /resources/example://test/{id}/disable
```

### Prompts

```http
POST /prompts/{prompt_name}/enable
POST /prompts/{prompt_name}/disable
```
---

#### 🧪 Example Response

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "message": "Disabled tool: example_tool"
}

```

---

## ⚙️ Configuration Options

### Custom Root Path

To mount the API under a different path:

```python
set_up_component_manager(server=mcp, path="/admin")
```

### Securing Endpoints with Auth Scopes

If your server uses authentication:

```python
mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
set_up_component_manager(server=mcp, required_scopes=["write", "read"])
```

---

## 🧪 Example: Enabling a Tool with Curl

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  http://localhost:8001/tools/example_tool/enable
```

---

## 🧱 Working with Mounted Servers

You can also combine different configurations when working with mounted servers — for example, using different scopes:

```python
mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
set_up_component_manager(server=mcp, required_scopes=["mcp:write"])

mounted = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
set_up_component_manager(server=mounted, required_scopes=["mounted:write"])

mcp.mount(server=mounted, prefix="mo")
```

This allows you to grant different levels of access:

```bash
# Accessing the main server gives you control over both local and mounted components
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  http://localhost:8001/tools/mo_example_tool/enable

# Accessing the mounted server gives you control only over its own components
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  http://localhost:8002/tools/example_tool/enable
```

---

## ⚙️ How It Works

- `set_up_component_manager()` registers API routes for tools, resources, and prompts.
- The `ComponentService` class exposes async methods to enable/disable components.
- Each endpoint returns a success message in JSON or a 404 error if the component isn't found.

---

## 🧩 Extending

You can subclass `ComponentService` for custom behavior or mount its routes elsewhere as needed.

---

## Maintenance Notice

This module is not officially maintained by the core FastMCP team. It is an independent extension developed by [gorocode](https://github.com/gorocode).

If you encounter any issues or wish to contribute, please feel free to open an issue or submit a pull request, and kindly notify me. I'd love to stay up to date.


## 📄 License

This module follows the license of the main [FastMCP](https://github.com/jlowin/fastmcp) project.

================
File: src/fastmcp/contrib/mcp_mixin/__init__.py
================
__all__ = [

================
File: src/fastmcp/contrib/mcp_mixin/example.py
================
"""Sample code for FastMCP using MCPMixin."""
⋮----
mcp = FastMCP()
class Sample(MCPMixin)
⋮----
def __init__(self, name)
⋮----
@mcp_tool()
    def first_tool(self)
⋮----
"""First tool description."""
⋮----
@mcp_resource(uri="test://test")
    def first_resource(self)
⋮----
"""First resource description."""
⋮----
@mcp_prompt()
    def first_prompt(self)
⋮----
"""First prompt description."""
⋮----
first_sample = Sample("First")
second_sample = Sample("Second")
⋮----
async def list_components()

================
File: src/fastmcp/contrib/mcp_mixin/mcp_mixin.py
================
"""Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""
⋮----
_MCP_REGISTRATION_TOOL_ATTR = "_mcp_tool_registration"
_MCP_REGISTRATION_RESOURCE_ATTR = "_mcp_resource_registration"
_MCP_REGISTRATION_PROMPT_ATTR = "_mcp_prompt_registration"
_DEFAULT_SEPARATOR_TOOL = "_"
_DEFAULT_SEPARATOR_RESOURCE = "+"
_DEFAULT_SEPARATOR_PROMPT = "_"
⋮----
"""Decorator to mark a method as an MCP tool for later registration."""
def decorator(func: Callable[..., Any]) -> Callable[..., Any]
⋮----
call_args = {
call_args = {k: v for k, v in call_args.items() if v is not None}
⋮----
"""Decorator to mark a method as an MCP resource for later registration."""
⋮----
"""Decorator to mark a method as an MCP prompt for later registration."""
⋮----
class MCPMixin
⋮----
"""Base mixin class for objects that can register tools, resources, and prompts
    with a FastMCP server instance using decorators.
    This mixin provides methods like `register_all`, `register_tools`, etc.,
    which iterate over the methods of the inheriting class, find methods
    decorated with `@mcp_tool`, `@mcp_resource`, or `@mcp_prompt`, and
    register them with the provided FastMCP server instance.
    """
def _get_methods_to_register(self, registration_type: str)
⋮----
"""Retrieves all methods marked for a specific registration type."""
⋮----
"""Registers all methods marked with @mcp_tool with the FastMCP server.
        Args:
            mcp_server: The FastMCP server instance to register tools with.
            prefix: Optional prefix to prepend to tool names. If provided, the
                final name will be f"{prefix}{separator}{original_name}".
            separator: The separator string used between prefix and original name.
                Defaults to '_'.
        """
⋮----
tool = Tool.from_function(fn=method, **registration_info)
⋮----
"""Registers all methods marked with @mcp_resource with the FastMCP server.
        Args:
            mcp_server: The FastMCP server instance to register resources with.
            prefix: Optional prefix to prepend to resource names and URIs. If provided,
                the final name will be f"{prefix}{separator}{original_name}" and the
                final URI will be f"{prefix}{separator}{original_uri}".
            separator: The separator string used between prefix and original name/URI.
                Defaults to '+'.
        """
⋮----
resource = Resource.from_function(fn=method, **registration_info)
⋮----
"""Registers all methods marked with @mcp_prompt with the FastMCP server.
        Args:
            mcp_server: The FastMCP server instance to register prompts with.
            prefix: Optional prefix to prepend to prompt names. If provided, the
                final name will be f"{prefix}{separator}{original_name}".
            separator: The separator string used between prefix and original name.
                Defaults to '_'.
        """
⋮----
prompt = Prompt.from_function(fn=method, **registration_info)
⋮----
"""Registers all marked tools, resources, and prompts with the server.
        This method calls `register_tools`, `register_resources`, and `register_prompts`
        internally, passing the provided prefix and separators.
        Args:
            mcp_server: The FastMCP server instance to register with.
            prefix: Optional prefix applied to all registered items unless overridden
                by a specific separator argument.
            tool_separator: Separator for tool names (defaults to '_').
            resource_separator: Separator for resource names/URIs (defaults to '+').
            prompt_separator: Separator for prompt names (defaults to '_').
        """

================
File: src/fastmcp/contrib/mcp_mixin/README.md
================
from mcp.types import ToolAnnotations

# MCP Mixin

This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).

It allows developers to easily define classes whose methods can be registered as tools, resources, or prompts with a `FastMCP` server instance using the `register_all()`, `register_tools()`, `register_resources()`, or `register_prompts()` methods provided by the mixin.

Includes support for
Tools:
* [enable/disable](https://gofastmcp.com/servers/tools#disabling-tools)
* [annotations](https://gofastmcp.com/servers/tools#annotations-2)
* [excluded arguments](https://gofastmcp.com/servers/tools#excluding-arguments)

Prompts:
* [enable/disable](https://gofastmcp.com/servers/prompts#disabling-prompts)

Resources:
* [enable/disabe](https://gofastmcp.com/servers/resources#disabling-resources)
  
## Usage

Inherit from `MCPMixin` and use the decorators on the methods you want to register.

```python
from mcp.types import ToolAnnotations
from fastmcp import FastMCP
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt

class MyComponent(MCPMixin):
    @mcp_tool(name="my_tool", description="Does something cool.")
    def tool_method(self):
        return "Tool executed!"

    # example of disabled tool
    @mcp_tool(name="my_tool", description="Does something cool.", enabled=False)
    def disabled_tool_method(self):
        # This function can't be called by client because it's disabled
        return "You'll never get here!"

    # example of excluded parameter tool
    @mcp_tool(
        name="my_tool", description="Does something cool.",
        enabled=False, exclude_args=['delete_everything'],
    )
    def excluded_param_tool_method(self, delete_everything=False):
        # MCP tool calls can't pass the "delete_everything" argument
        if delete_everything:
            return "Nothing to delete, I bet you're not a tool :)"
        return "You might be a tool if..."

    # example tool w/annotations
    @mcp_tool(
        name="my_tool", description="Does something cool.",
        annotations=ToolAnnotations(
            title="Attn LLM, use this tool first!",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
        )
    )
    def tool_method(self):
        return "Tool executed!"

    # example tool w/everything
    @mcp_tool(
        name="my_tool", description="Does something cool.",
        enabled=True,
        exclude_args=['delete_all'],
        annotations=ToolAnnotations(
            title="Attn LLM, use this tool first!",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
        )
    )
    def tool_method(self, delete_all=False):
        if delete_all:
            return "99 records deleted. I bet you're not a tool :)"
        return "Tool executed, but you might be a tool!"
    
    @mcp_resource(uri="component://data")
    def resource_method(self):
        return {"data": "some data"}

    # Disabled resource
    @mcp_resource(uri="component://data", enabled=False)
    def resource_method(self):
        return {"data": "some data"}

    # prompt
    @mcp_prompt(name="A prompt")
    def prompt_method(self, name):
        return f"Whats up {name}?"

    # disabled prompt
    @mcp_prompt(name="A prompt", enabled=False)
    def prompt_method(self, name):
        return f"Whats up {name}?"

mcp_server = FastMCP()
component = MyComponent()

# Register all decorated methods with a prefix
# Useful if you will have multiple instantiated objects of the same class
# and want to avoid name collisions.
component.register_all(mcp_server, prefix="my_comp") 

# Register without a prefix
# component.register_all(mcp_server) 

# Now 'my_comp_my_tool' tool and 'my_comp+component://data' resource are registered (if prefix used)
# Or 'my_tool' and 'component://data' are registered (if no prefix used)
```

The `prefix` argument in registration methods is optional. If omitted, methods are registered with their original decorated names/URIs. Individual separators (`tools_separator`, `resources_separator`, `prompts_separator`) can also be provided to `register_all` to change the separator for specific types.

================
File: src/fastmcp/contrib/README.md
================
# FastMCP Contrib Modules

This directory holds community-contributed modules for FastMCP. These modules extend FastMCP's functionality but are not officially maintained by the core team.

**Guarantees:**
*   Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
*   Changes to the core FastMCP library might break modules in `contrib` without explicit warnings in the main changelog.

Use these modules at your own discretion. Contributions are welcome, but please include tests and documentation.

## Usage

To use a contrib module, import it from the `fastmcp.contrib` package.

```python
from fastmcp.contrib import my_module
```

Note that the contrib modules may have different dependencies than the core library, which can be noted in their respective README's or even separate requirements / dependency files.

================
File: src/fastmcp/prompts/__init__.py
================
__all__ = [

================
File: src/fastmcp/prompts/prompt_manager.py
================
logger = get_logger(__name__)
class PromptManager
⋮----
"""Manages FastMCP prompts."""
⋮----
# Default to "warn" if None is provided
⋮----
duplicate_behavior = "warn"
⋮----
def mount(self, server: MountedServer) -> None
⋮----
"""Adds a mounted server as a source for prompts."""
⋮----
async def _load_prompts(self, *, via_server: bool = False) -> dict[str, Prompt]
⋮----
"""
        The single, consolidated recursive method for fetching prompts. The 'via_server'
        parameter determines the communication path.
        - via_server=False: Manager-to-manager path for complete, unfiltered inventory
        - via_server=True: Server-to-server path for filtered MCP requests
        """
all_prompts: dict[str, Prompt] = {}
⋮----
# Use the server-to-server filtered path
child_results = await mounted.server._list_prompts()
⋮----
# Use the manager-to-manager unfiltered path
child_results = await mounted.server._prompt_manager.list_prompts()
# The combination logic is the same for both paths
child_dict = {p.key: p for p in child_results}
⋮----
prefixed_prompt = prompt.with_key(
⋮----
# Skip failed mounts silently, matches existing behavior
⋮----
# Finally, add local prompts, which always take precedence
⋮----
async def has_prompt(self, key: str) -> bool
⋮----
"""Check if a prompt exists."""
prompts = await self.get_prompts()
⋮----
async def get_prompt(self, key: str) -> Prompt
⋮----
"""Get prompt by key."""
⋮----
async def get_prompts(self) -> dict[str, Prompt]
⋮----
"""
        Gets the complete, unfiltered inventory of all prompts.
        """
⋮----
async def list_prompts(self) -> list[Prompt]
⋮----
"""
        Lists all prompts, applying protocol filtering.
        """
prompts_dict = await self._load_prompts(via_server=True)
⋮----
"""Create a prompt from a function."""
# deprecated in 2.7.0
⋮----
prompt = FunctionPrompt.from_function(
return self.add_prompt(prompt)  # type: ignore
def add_prompt(self, prompt: Prompt) -> Prompt
⋮----
"""Add a prompt to the manager."""
# Check for duplicates
existing = self._prompts.get(prompt.key)
⋮----
"""
        Internal API for servers: Finds and renders a prompt, respecting the
        filtered protocol path.
        """
# 1. Check local prompts first. The server will have already applied its filter.
⋮----
prompt = await self.get_prompt(name)
⋮----
messages = await prompt.render(arguments)
⋮----
# Pass through PromptErrors as-is
⋮----
# Handle other exceptions
⋮----
# Mask internal details
⋮----
# Include original error details
⋮----
# 2. Check mounted servers using the filtered protocol path.
⋮----
prompt_key = name
⋮----
prompt_key = name.removeprefix(f"{mounted.prefix}_")

================
File: src/fastmcp/prompts/prompt.py
================
"""Base classes for FastMCP prompts."""
⋮----
logger = get_logger(__name__)
⋮----
"""A user-friendly constructor for PromptMessage."""
⋮----
content = TextContent(type="text", text=content)
⋮----
role = "user"
⋮----
message_validator = TypeAdapter[PromptMessage](PromptMessage)
SyncPromptResult = (
PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]
class PromptArgument(FastMCPBaseModel)
⋮----
"""An argument that can be passed to a prompt."""
name: str = Field(description="Name of the argument")
description: str | None = Field(
required: bool = Field(
class Prompt(FastMCPComponent, ABC)
⋮----
"""A prompt template that can be rendered with parameters."""
arguments: list[PromptArgument] | None = Field(
def enable(self) -> None
⋮----
context = get_context()
context._queue_prompt_list_changed()  # type: ignore[private-use]
⋮----
pass  # No context available
def disable(self) -> None
def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt
⋮----
"""Convert the prompt to an MCP prompt."""
arguments = [
kwargs = {
⋮----
"""Create a Prompt from a function.
        The function can return:
        - A string (converted to a message)
        - A Message object
        - A dict (converted to a message)
        - A sequence of any of the above
        """
⋮----
"""Render the prompt with arguments."""
⋮----
class FunctionPrompt(Prompt)
⋮----
"""A prompt that is a function."""
fn: Callable[..., PromptResult | Awaitable[PromptResult]]
⋮----
func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
⋮----
# Reject functions with *args or **kwargs
sig = inspect.signature(fn)
⋮----
description = description or inspect.getdoc(fn)
# if the fn is a callable class, we need to get the __call__ method from here out
⋮----
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
⋮----
fn = fn.__func__
type_adapter = get_cached_typeadapter(fn)
parameters = type_adapter.json_schema()
# Auto-detect context parameter if not provided
context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
⋮----
prune_params = [context_kwarg]
⋮----
prune_params = None
parameters = compress_schema(parameters, prune_params=prune_params)
# Convert parameters to PromptArguments
arguments: list[PromptArgument] = []
⋮----
arg_description = param.get("description")
# For non-string parameters, append JSON schema info to help users
# understand the expected format when passing as strings (MCP requirement)
⋮----
sig_param = sig.parameters[param_name]
⋮----
# Get the JSON schema for this specific parameter type
⋮----
param_adapter = get_cached_typeadapter(sig_param.annotation)
param_schema = param_adapter.json_schema()
# Create compact schema representation
schema_str = json.dumps(param_schema, separators=(",", ":"))
# Append schema info to description
schema_note = f"Provide as a JSON string matching the following schema: {schema_str}"
⋮----
arg_description = f"{arg_description}\n\n{schema_note}"
⋮----
arg_description = schema_note
⋮----
# If schema generation fails, skip enhancement
⋮----
def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]
⋮----
"""Convert string arguments to expected types based on function signature."""
⋮----
sig = inspect.signature(self.fn)
converted_kwargs = {}
# Find context parameter name if any
context_param_name = find_kwarg_by_type(self.fn, kwarg_type=Context)
⋮----
param = sig.parameters[param_name]
# Skip Context parameters - they're handled separately
⋮----
# If parameter has no annotation or annotation is str, pass as-is
⋮----
# If argument is not a string, pass as-is (already properly typed)
⋮----
# Try to convert string argument using type adapter
⋮----
adapter = get_cached_typeadapter(param.annotation)
# Try JSON parsing first for complex types
⋮----
# Fallback to direct validation
⋮----
# If conversion fails, provide informative error
⋮----
# Parameter not in function signature, pass as-is
⋮----
# Validate required arguments
⋮----
required = {arg.name for arg in self.arguments if arg.required}
provided = set(arguments or {})
missing = required - provided
⋮----
# Prepare arguments with context
kwargs = arguments.copy() if arguments else {}
context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
⋮----
# Convert string arguments to expected types when needed
kwargs = self._convert_string_arguments(kwargs)
# Call function and check if result is a coroutine
result = self.fn(**kwargs)
⋮----
result = await result
# Validate messages
⋮----
result = [result]
# Convert result to messages
messages: list[PromptMessage] = []
⋮----
content = pydantic_core.to_json(

================
File: src/fastmcp/resources/__init__.py
================
__all__ = [

================
File: src/fastmcp/resources/resource_manager.py
================
"""Resource manager functionality."""
⋮----
logger = get_logger(__name__)
class ResourceManager
⋮----
"""Manages FastMCP resources."""
⋮----
"""Initialize the ResourceManager.
        Args:
            duplicate_behavior: How to handle duplicate resources
                (warn, error, replace, ignore)
            mask_error_details: Whether to mask error details from exceptions
                other than ResourceError
        """
⋮----
# Default to "warn" if None is provided
⋮----
duplicate_behavior = "warn"
⋮----
def mount(self, server: MountedServer) -> None
⋮----
"""Adds a mounted server as a source for resources and templates."""
⋮----
async def get_resources(self) -> dict[str, Resource]
⋮----
"""Get all registered resources, keyed by URI."""
⋮----
async def get_resource_templates(self) -> dict[str, ResourceTemplate]
⋮----
"""Get all registered templates, keyed by URI template."""
⋮----
async def _load_resources(self, *, via_server: bool = False) -> dict[str, Resource]
⋮----
"""
        The single, consolidated recursive method for fetching resources. The 'via_server'
        parameter determines the communication path.
        - via_server=False: Manager-to-manager path for complete, unfiltered inventory
        - via_server=True: Server-to-server path for filtered MCP requests
        """
all_resources: dict[str, Resource] = {}
⋮----
# Use the server-to-server filtered path
child_resources_list = await mounted.server._list_resources()
child_resources = {
⋮----
# Use the manager-to-manager unfiltered path
child_resources = (
# Apply prefix if needed
⋮----
prefixed_uri = add_resource_prefix(
# Create a copy of the resource with the prefixed key
prefixed_resource = resource.with_key(prefixed_uri)
⋮----
# Skip failed mounts silently, matches existing behavior
⋮----
# Finally, add local resources, which always take precedence
⋮----
"""
        The single, consolidated recursive method for fetching templates. The 'via_server'
        parameter determines the communication path.
        - via_server=False: Manager-to-manager path for complete, unfiltered inventory
        - via_server=True: Server-to-server path for filtered MCP requests
        """
all_templates: dict[str, ResourceTemplate] = {}
⋮----
child_templates = await mounted.server._list_resource_templates()
⋮----
child_templates = (
child_dict = {template.key: template for template in child_templates}
⋮----
prefixed_uri_template = add_resource_prefix(
# Create a copy of the template with the prefixed key
prefixed_template = template.with_key(prefixed_uri_template)
⋮----
# Finally, add local templates, which always take precedence
⋮----
async def list_resources(self) -> list[Resource]
⋮----
"""
        Lists all resources, applying protocol filtering.
        """
resources_dict = await self._load_resources(via_server=True)
⋮----
async def list_resource_templates(self) -> list[ResourceTemplate]
⋮----
"""
        Lists all templates, applying protocol filtering.
        """
templates_dict = await self._load_resource_templates(via_server=True)
⋮----
"""Add a resource or template to the manager from a function.
        Args:
            fn: The function to register as a resource or template
            uri: The URI for the resource or template
            name: Optional name for the resource or template
            description: Optional description of the resource or template
            mime_type: Optional MIME type for the resource or template
            tags: Optional set of tags for categorizing the resource or template
        Returns:
            The added resource or template. If a resource or template with the same URI already exists,
            returns the existing resource or template.
        """
⋮----
# Check if this should be a template
has_uri_params = "{" in uri and "}" in uri
# check if the function has any parameters (other than injected context)
has_func_params = any(
⋮----
"""Add a resource to the manager from a function.
        Args:
            fn: The function to register as a resource
            uri: The URI for the resource
            name: Optional name for the resource
            description: Optional description of the resource
            mime_type: Optional MIME type for the resource
            tags: Optional set of tags for categorizing the resource
        Returns:
            The added resource. If a resource with the same URI already exists,
            returns the existing resource.
        """
# deprecated in 2.7.0
⋮----
resource = Resource.from_function(
⋮----
def add_resource(self, resource: Resource) -> Resource
⋮----
"""Add a resource to the manager.
        Args:
            resource: A Resource instance to add. The resource's .key attribute
                will be used as the storage key. To overwrite it, call
                Resource.with_key() before calling this method.
        """
existing = self._resources.get(resource.key)
⋮----
"""Create a template from a function."""
⋮----
template = ResourceTemplate.from_function(
⋮----
def add_template(self, template: ResourceTemplate) -> ResourceTemplate
⋮----
"""Add a template to the manager.
        Args:
            template: A ResourceTemplate instance to add. The template's .key attribute
                will be used as the storage key. To overwrite it, call
                ResourceTemplate.with_key() before calling this method.
        Returns:
            The added template. If a template with the same URI already exists,
            returns the existing template.
        """
existing = self._templates.get(template.key)
⋮----
async def has_resource(self, uri: AnyUrl | str) -> bool
⋮----
"""Check if a resource exists."""
uri_str = str(uri)
# First check concrete resources (local and mounted)
resources = await self.get_resources()
⋮----
# Then check templates (local and mounted) only if not found in concrete resources
templates = await self.get_resource_templates()
⋮----
async def get_resource(self, uri: AnyUrl | str) -> Resource
⋮----
"""Get resource by URI, checking concrete resources first, then templates.
        Args:
            uri: The URI of the resource to get
        Raises:
            NotFoundError: If no resource or template matching the URI is found.
        """
⋮----
# Then check templates (local and mounted) - use the utility function to match against storage keys
⋮----
# Try to match against the storage key (which might be a custom key)
⋮----
# Pass through ResourceErrors as-is
⋮----
# Handle other exceptions
⋮----
# Mask internal details
⋮----
# Include original error details
⋮----
async def read_resource(self, uri: AnyUrl | str) -> str | bytes
⋮----
"""
        Internal API for servers: Finds and reads a resource, respecting the
        filtered protocol path.
        """
⋮----
# 1. Check local resources first. The server will have already applied its filter.
⋮----
resource = await self.get_resource(uri_str)
⋮----
# raise ResourceErrors as-is
⋮----
# Handle other exceptions
⋮----
# Mask internal details
⋮----
# Include original error details
⋮----
# 1b. Check local templates if not found in concrete resources
⋮----
resource = await template.create_resource(uri_str, params=params)
⋮----
# 2. Check mounted servers using the filtered protocol path.
⋮----
key = uri_str
⋮----
key = remove_resource_prefix(
⋮----
result = await mounted.server._read_resource(key)

================
File: src/fastmcp/resources/resource.py
================
"""Base classes and interfaces for FastMCP resources."""
⋮----
class Resource(FastMCPComponent, abc.ABC)
⋮----
"""Base class for all resources."""
model_config = ConfigDict(validate_default=True)
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
name: str = Field(default="", description="Name of the resource")
mime_type: str = Field(
def enable(self) -> None
⋮----
context = get_context()
context._queue_resource_list_changed()  # type: ignore[private-use]
⋮----
pass  # No context available
def disable(self) -> None
⋮----
@field_validator("mime_type", mode="before")
@classmethod
    def set_default_mime_type(cls, mime_type: str | None) -> str
⋮----
"""Set default MIME type if not provided."""
⋮----
@model_validator(mode="after")
    def set_default_name(self) -> Self
⋮----
"""Set default name from URI if not provided."""
⋮----
@abc.abstractmethod
    async def read(self) -> str | bytes
⋮----
"""Read the resource content."""
⋮----
def to_mcp_resource(self, **overrides: Any) -> MCPResource
⋮----
"""Convert the resource to an MCPResource."""
kwargs = {
⋮----
def __repr__(self) -> str
⋮----
@property
    def key(self) -> str
⋮----
"""
        The key of the component. This is used for internal bookkeeping
        and may reflect e.g. prefixes or other identifiers. You should not depend on
        keys having a certain value, as the same tool loaded from different
        hierarchies of servers may have different keys.
        """
⋮----
class FunctionResource(Resource)
⋮----
"""A resource that defers data loading by wrapping a function.
    The function is only called when the resource is read, allowing for lazy loading
    of potentially expensive data. This is particularly useful when listing resources,
    as the function won't be called until the resource is actually accessed.
    The function can return:
    - str for text content (default)
    - bytes for binary content
    - other types will be converted to JSON
    """
fn: Callable[[], Any]
⋮----
"""Create a FunctionResource from a function."""
⋮----
uri = AnyUrl(uri)
⋮----
async def read(self) -> str | bytes
⋮----
"""Read the resource by calling the wrapped function."""
⋮----
kwargs = {}
context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
⋮----
result = self.fn(**kwargs)
⋮----
result = await result

================
File: src/fastmcp/resources/template.py
================
"""Resource template functionality."""
⋮----
def build_regex(template: str) -> re.Pattern
⋮----
parts = re.split(r"(\{[^}]+\})", template)
pattern = ""
⋮----
name = part[1:-1]
⋮----
name = name[:-1]
⋮----
def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
⋮----
regex = build_regex(uri_template)
match = regex.match(uri)
⋮----
class ResourceTemplate(FastMCPComponent)
⋮----
"""A template for dynamically creating resources."""
uri_template: str = Field(
mime_type: str = Field(
parameters: dict[str, Any] = Field(
def __repr__(self) -> str
def enable(self) -> None
⋮----
context = get_context()
context._queue_resource_list_changed()  # type: ignore[private-use]
⋮----
pass  # No context available
def disable(self) -> None
⋮----
@field_validator("mime_type", mode="before")
@classmethod
    def set_default_mime_type(cls, mime_type: str | None) -> str
⋮----
"""Set default MIME type if not provided."""
⋮----
def matches(self, uri: str) -> dict[str, Any] | None
⋮----
"""Check if URI matches template and extract parameters."""
⋮----
async def read(self, arguments: dict[str, Any]) -> str | bytes
⋮----
"""Read the resource content."""
⋮----
async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource
⋮----
"""Create a resource from the template with the given parameters."""
async def resource_read_fn() -> str | bytes
⋮----
# Call function and check if result is a coroutine
result = await self.read(arguments=params)
⋮----
def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate
⋮----
"""Convert the resource template to an MCPResourceTemplate."""
kwargs = {
⋮----
@classmethod
    def from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate
⋮----
"""Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object."""
# Note: This creates a simple ResourceTemplate instance. For function-based templates,
# the original function is lost, which is expected for remote templates.
⋮----
parameters={},  # Remote templates don't have local parameters
⋮----
@property
    def key(self) -> str
⋮----
"""
        The key of the component. This is used for internal bookkeeping
        and may reflect e.g. prefixes or other identifiers. You should not depend on
        keys having a certain value, as the same tool loaded from different
        hierarchies of servers may have different keys.
        """
⋮----
class FunctionResourceTemplate(ResourceTemplate)
⋮----
fn: Callable[..., Any]
⋮----
# Add context to parameters if needed
kwargs = arguments.copy()
context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
⋮----
result = self.fn(**kwargs)
⋮----
result = await result
⋮----
"""Create a template from a function."""
⋮----
func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
⋮----
# Reject functions with *args
# (**kwargs is allowed because the URI will define the parameter names)
sig = inspect.signature(fn)
⋮----
# Auto-detect context parameter if not provided
context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
# Validate that URI params match function params
uri_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
⋮----
func_params = set(sig.parameters.keys())
⋮----
# get the parameters that are required
required_params = {
# Check if required parameters are a subset of the URI parameters
⋮----
# Check if the URI parameters are a subset of the function parameters (skip if **kwargs present)
⋮----
description = description or inspect.getdoc(fn)
# if the fn is a callable class, we need to get the __call__ method from here out
⋮----
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
⋮----
fn = fn.__func__
type_adapter = get_cached_typeadapter(fn)
parameters = type_adapter.json_schema()
# compress the schema
prune_params = [context_kwarg] if context_kwarg else None
parameters = compress_schema(parameters, prune_params=prune_params)
# ensure the arguments are properly cast
fn = validate_call(fn)

================
File: src/fastmcp/resources/types.py
================
"""Concrete resource implementations."""
⋮----
logger = get_logger(__name__)
class TextResource(Resource)
⋮----
"""A resource that reads from a string."""
text: str = Field(description="Text content of the resource")
async def read(self) -> str
⋮----
"""Read the text content."""
⋮----
class BinaryResource(Resource)
⋮----
"""A resource that reads from bytes."""
data: bytes = Field(description="Binary content of the resource")
async def read(self) -> bytes
⋮----
"""Read the binary content."""
⋮----
class FileResource(Resource)
⋮----
"""A resource that reads from a file.
    Set is_binary=True to read file as binary data instead of text.
    """
path: Path = Field(description="Path to the file")
is_binary: bool = Field(
mime_type: str = Field(
⋮----
@pydantic.field_validator("path")
@classmethod
    def validate_absolute_path(cls, path: Path) -> Path
⋮----
"""Ensure path is absolute."""
⋮----
@pydantic.field_validator("is_binary")
@classmethod
    def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool
⋮----
"""Set is_binary based on mime_type if not explicitly set."""
⋮----
mime_type = info.data.get("mime_type", "text/plain")
⋮----
async def read(self) -> str | bytes
⋮----
"""Read the file content."""
⋮----
class HttpResource(Resource)
⋮----
"""A resource that reads from an HTTP endpoint."""
url: str = Field(description="URL to fetch content from")
⋮----
"""Read the HTTP content."""
⋮----
response = await client.get(self.url)
⋮----
class DirectoryResource(Resource)
⋮----
"""A resource that lists files in a directory."""
path: Path = Field(description="Path to the directory")
recursive: bool = Field(
pattern: str | None = Field(
⋮----
def list_files(self) -> list[Path]
⋮----
"""List files in the directory."""
⋮----
async def read(self) -> str:  # Always returns JSON string
⋮----
"""Read the directory listing."""
⋮----
files = await anyio.to_thread.run_sync(self.list_files)
file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()]

================
File: src/fastmcp/server/auth/providers/bearer_env.py
================
class EnvBearerAuthProviderSettings(BaseSettings)
⋮----
"""Settings for the BearerAuthProvider."""
model_config = SettingsConfigDict(
public_key: str | None = None
jwks_uri: str | None = None
issuer: str | None = None
audience: str | None = None
required_scopes: list[str] | None = None
class EnvBearerAuthProvider(BearerAuthProvider)
⋮----
"""
    A BearerAuthProvider that loads settings from environment variables. Any
    providing setting will always take precedence over the environment
    variables.
    """
⋮----
"""
        Initialize the provider.
        Args:
            public_key: RSA public key in PEM format (for static key)
            jwks_uri: URI to fetch keys from (for key rotation)
            issuer: Expected issuer claim (optional)
            audience: Expected audience claim (optional)
            required_scopes: List of required scopes for access (optional)
        """
kwargs = {
settings = EnvBearerAuthProviderSettings(

================
File: src/fastmcp/server/auth/providers/bearer.py
================
class JWKData(TypedDict, total=False)
⋮----
"""JSON Web Key data structure."""
kty: str  # Key type (e.g., "RSA") - required
kid: str  # Key ID (optional but recommended)
use: str  # Usage (e.g., "sig")
alg: str  # Algorithm (e.g., "RS256")
n: str  # Modulus (for RSA keys)
e: str  # Exponent (for RSA keys)
x5c: list[str]  # X.509 certificate chain (for JWKs)
x5t: str  # X.509 certificate thumbprint (for JWKs)
class JWKSData(TypedDict)
⋮----
"""JSON Web Key Set data structure."""
keys: list[JWKData]
⋮----
@dataclass(frozen=True, kw_only=True, repr=False)
class RSAKeyPair
⋮----
private_key: SecretStr
public_key: str
⋮----
@classmethod
    def generate(cls) -> "RSAKeyPair"
⋮----
"""
        Generate an RSA key pair for testing.
        Returns:
            tuple: (private_key_pem, public_key_pem)
        """
# Generate private key
private_key = rsa.generate_private_key(
# Get public key
public_key = private_key.public_key()
# Serialize private key to PEM format
private_pem = private_key.private_bytes(
# Serialize public key to PEM format
public_pem = public_key.public_bytes(
⋮----
"""
        Generate a test JWT token for testing purposes.
        Args:
            private_key_pem: RSA private key in PEM format
            subject: Subject claim (usually user ID)
            issuer: Issuer claim
            audience: Audience claim - can be a string or list of strings (optional)
            scopes: List of scopes to include
            expires_in_seconds: Token expiration time in seconds
            additional_claims: Any additional claims to include
            kid: Key ID for JWKS lookup (optional)
        Returns:
            Signed JWT token string
        """
jwt = JsonWebToken(["RS256"])
now = int(time.time())
# Build payload
payload = {
⋮----
# Create header
header = {"alg": "RS256"}
⋮----
# Sign and return token
token_bytes = jwt.encode(
⋮----
class BearerAuthProvider(OAuthProvider)
⋮----
"""
    Simple JWT Bearer Token validator for hosted MCP servers.
    Uses RS256 asymmetric encryption. Supports either static public key
    or JWKS URI for key rotation.
    Note that this provider DOES NOT permit client registration or revocation, or any OAuth flows.
    It is intended to be used with a control plane that manages clients and tokens.
    """
⋮----
"""
        Initialize the provider. Either public_key or jwks_uri must be provided.
        Args:
            public_key: RSA public key in PEM format (for static key)
            jwks_uri: URI to fetch keys from (for key rotation)
            issuer: Expected issuer claim (optional)
            audience: Expected audience claim - can be a string or list of strings (optional)
            required_scopes: List of required scopes for access (optional)
        """
⋮----
# Only pass issuer to parent if it's a valid URL, otherwise use default
# This allows the issuer claim validation to work with string issuers per RFC 7519
⋮----
issuer_url = AnyHttpUrl(issuer) if issuer else "https://fastmcp.example.com"
⋮----
# Issuer is not a valid URL, use default for parent class
issuer_url = "https://fastmcp.example.com"
⋮----
# Simple JWKS cache
⋮----
self._cache_ttl = 3600  # 1 hour
async def _get_verification_key(self, token: str) -> str
⋮----
"""Get the verification key for the token."""
⋮----
# Extract kid from token header for JWKS lookup
⋮----
header_b64 = token.split(".")[0]
header_b64 += "=" * (4 - len(header_b64) % 4)  # Add padding
header = json.loads(base64.urlsafe_b64decode(header_b64))
kid = header.get("kid")
⋮----
async def _get_jwks_key(self, kid: str | None) -> str
⋮----
"""Fetch key from JWKS with simple caching."""
⋮----
current_time = time.time()
# Check cache first
⋮----
# If no kid but only one key cached, use it
⋮----
# Fetch JWKS
⋮----
response = await client.get(self.jwks_uri)
⋮----
jwks_data = response.json()
# Cache all keys
⋮----
key_kid = key_data.get("kid")
jwk = JsonWebKey.import_key(key_data)
public_key = jwk.get_public_key()  # type: ignore
⋮----
# Key without kid - use a default identifier
⋮----
# Select the appropriate key
⋮----
# No kid in token - only allow if there's exactly one key
⋮----
async def load_access_token(self, token: str) -> AccessToken | None
⋮----
"""
        Validates the provided JWT bearer token.
        Args:
            token: The JWT token string to validate
        Returns:
            AccessToken object if valid, None if invalid or expired
        """
⋮----
# Get verification key (static or from JWKS)
verification_key = await self._get_verification_key(token)
# Decode and verify the JWT token
claims = self.jwt.decode(token, verification_key)
# Extract client ID early for logging
client_id = claims.get("client_id") or claims.get("sub") or "unknown"
# Validate expiration
exp = claims.get("exp")
⋮----
# Validate issuer - note we use issuer instead of issuer_url here because
# issuer is optional, allowing users to make this check optional
⋮----
# Validate audience if configured
⋮----
aud = claims.get("aud")
# Handle different combinations of audience types
audience_valid = False
⋮----
# self.audience is a list - check if any expected audience is present
⋮----
# Both are lists - check for intersection
audience_valid = any(
⋮----
# aud is a string - check if it's in our expected list
audience_valid = aud in self.audience
⋮----
# self.audience is a string - use original logic
⋮----
audience_valid = self.audience in aud
⋮----
audience_valid = aud == self.audience
⋮----
# Extract scopes
scopes = self._extract_scopes(claims)
⋮----
def _extract_scopes(self, claims: dict[str, Any]) -> list[str]
⋮----
"""Extract scopes from JWT claims."""
scope_claim = claims.get("scope", "")
⋮----
async def verify_token(self, token: str) -> AccessToken | None
⋮----
"""
        Verify a bearer token and return access info if valid.
        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.
        Args:
            token: The JWT token string to validate
        Returns:
            AccessToken object if valid, None if invalid or expired
        """
⋮----
# --- Unused OAuth server methods ---
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None
async def register_client(self, client_info: OAuthClientInformationFull) -> None

================
File: src/fastmcp/server/auth/providers/in_memory.py
================
# Default expiration times (in seconds)
DEFAULT_AUTH_CODE_EXPIRY_SECONDS = 5 * 60  # 5 minutes
DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS = 60 * 60  # 1 hour
DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS = None  # No expiry
class InMemoryOAuthProvider(OAuthProvider)
⋮----
"""
    An in-memory OAuth provider for testing purposes.
    It simulates the OAuth 2.1 flow locally without external calls.
    """
⋮----
# For revoking associated tokens
⋮----
] = {}  # access_token_str -> refresh_token_str
⋮----
] = {}  # refresh_token_str -> access_token_str
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None
async def register_client(self, client_info: OAuthClientInformationFull) -> None
⋮----
# As per RFC 7591, if client_id is already known, it's an update.
# For this simple provider, we'll treat it as re-registration.
# A real provider might handle updates or raise errors for conflicts.
⋮----
"""
        Simulates user authorization and generates an authorization code.
        Returns a redirect URI with the code and state.
        """
⋮----
# Validate redirect_uri (already validated by AuthorizationHandler, but good practice)
⋮----
# OAuthClientInformationFull should have a method like validate_redirect_uri
# For this test provider, we assume it's valid if it matches one in client_info
# The AuthorizationHandler already does robust validation using client.validate_redirect_uri
⋮----
# This check might be too simplistic if redirect_uris can be patterns
# or if params.redirect_uri is None and client has a default.
# However, the AuthorizationHandler handles the primary validation.
pass  # Let's assume AuthorizationHandler did its job.
except Exception:  # Replace with specific validation error if client.validate_redirect_uri existed
⋮----
auth_code_value = f"test_auth_code_{secrets.token_hex(16)}"
expires_at = time.time() + DEFAULT_AUTH_CODE_EXPIRY_SECONDS
# Ensure scopes are a list
scopes_list = params.scopes if params.scopes is not None else []
if client.scope:  # Filter params.scopes against client's registered scopes
client_allowed_scopes = set(client.scope.split())
scopes_list = [s for s in scopes_list if s in client_allowed_scopes]
auth_code = AuthorizationCode(
⋮----
# code_challenge_method is assumed S256 by the framework
⋮----
auth_code_obj = self.auth_codes.get(authorization_code)
⋮----
return None  # Belongs to a different client
⋮----
del self.auth_codes[authorization_code]  # Expired
⋮----
# Authorization code should have been validated (existence, expiry, client_id match)
# by the TokenHandler calling load_authorization_code before this.
# We might want to re-verify or simply trust it's valid.
⋮----
# Consume the auth code
⋮----
access_token_value = f"test_access_token_{secrets.token_hex(32)}"
refresh_token_value = f"test_refresh_token_{secrets.token_hex(32)}"
access_token_expires_at = int(time.time() + DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS)
# Refresh token expiry
refresh_token_expires_at = None
⋮----
refresh_token_expires_at = int(
⋮----
scopes=authorization_code.scopes,  # Refresh token inherits scopes
⋮----
token_obj = self.refresh_tokens.get(refresh_token)
⋮----
return None  # Belongs to different client
⋮----
)  # Clean up expired
⋮----
refresh_token: RefreshToken,  # This is the RefreshToken object, already loaded
scopes: list[str],  # Requested scopes for the new access token
⋮----
# Validate scopes: requested scopes must be a subset of original scopes
original_scopes = set(refresh_token.scopes)
requested_scopes = set(scopes)
⋮----
# Invalidate old refresh token and its associated access token (rotation)
⋮----
# Issue new tokens
new_access_token_value = f"test_access_token_{secrets.token_hex(32)}"
new_refresh_token_value = f"test_refresh_token_{secrets.token_hex(32)}"
⋮----
scopes=scopes,  # Use newly requested (and validated) scopes
⋮----
scopes=scopes,  # New refresh token also gets these scopes
⋮----
async def load_access_token(self, token: str) -> AccessToken | None
⋮----
token_obj = self.access_tokens.get(token)
⋮----
async def verify_token(self, token: str) -> AccessToken | None
⋮----
"""
        Verify a bearer token and return access info if valid.
        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.
        Args:
            token: The token string to validate
        Returns:
            AccessToken object if valid, None if invalid or expired
        """
⋮----
"""Internal helper to remove tokens and their associations."""
removed_access_token = None
removed_refresh_token = None
⋮----
removed_access_token = access_token_str
# Get associated refresh token
associated_refresh = self._access_to_refresh_map.pop(access_token_str, None)
⋮----
removed_refresh_token = associated_refresh
⋮----
removed_refresh_token = refresh_token_str
# Get associated access token
associated_access = self._refresh_to_access_map.pop(refresh_token_str, None)
⋮----
removed_access_token = associated_access
⋮----
# Clean up any dangling references if one part of the pair was already gone
⋮----
"""Revokes an access or refresh token and its counterpart."""
⋮----
# If token is not found or already revoked, _revoke_internal does nothing, which is correct.

================
File: src/fastmcp/server/auth/__init__.py
================
__all__ = ["BearerAuthProvider"]

================
File: src/fastmcp/server/auth/auth.py
================
class OAuthProvider(
⋮----
"""
        Initialize the OAuth provider.
        Args:
            issuer_url: The URL of the OAuth issuer.
            service_documentation_url: The URL of the service documentation.
            client_registration_options: The client registration options.
            revocation_options: The revocation options.
            required_scopes: Scopes that are required for all requests.
        """
⋮----
issuer_url = AnyHttpUrl(issuer_url)
⋮----
service_documentation_url = AnyHttpUrl(service_documentation_url)
⋮----
async def verify_token(self, token: str) -> AccessToken | None
⋮----
"""
        Verify a bearer token and return access info if valid.
        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.
        Args:
            token: The token string to validate
        Returns:
            AccessToken object if valid, None if invalid or expired
        """

================
File: src/fastmcp/server/middleware/__init__.py
================
__all__ = [

================
File: src/fastmcp/server/middleware/error_handling.py
================
"""Error handling middleware for consistent error responses and tracking."""
⋮----
class ErrorHandlingMiddleware(Middleware)
⋮----
"""Middleware that provides consistent error handling and logging.
    Catches exceptions, logs them appropriately, and converts them to
    proper MCP error responses. Also tracks error patterns for monitoring.
    Example:
        ```python
        from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
        import logging
        # Configure logging to see error details
        logging.basicConfig(level=logging.ERROR)
        mcp = FastMCP("MyServer")
        mcp.add_middleware(ErrorHandlingMiddleware())
        ```
    """
⋮----
"""Initialize error handling middleware.
        Args:
            logger: Logger instance for error logging. If None, uses 'fastmcp.errors'
            include_traceback: Whether to include full traceback in error logs
            error_callback: Optional callback function called for each error
            transform_errors: Whether to transform non-MCP errors to McpError
        """
⋮----
def _log_error(self, error: Exception, context: MiddlewareContext) -> None
⋮----
"""Log error with appropriate detail level."""
error_type = type(error).__name__
method = context.method or "unknown"
# Track error counts
error_key = f"{error_type}:{method}"
⋮----
base_message = f"Error in {method}: {error_type}: {str(error)}"
⋮----
# Call custom error callback if provided
⋮----
def _transform_error(self, error: Exception) -> Exception
⋮----
"""Transform non-MCP errors to proper MCP errors."""
⋮----
# Map common exceptions to appropriate MCP error codes
error_type = type(error)
⋮----
async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any
⋮----
"""Handle errors for all messages."""
⋮----
# Transform and re-raise
transformed_error = self._transform_error(error)
⋮----
def get_error_stats(self) -> dict[str, int]
⋮----
"""Get error statistics for monitoring."""
⋮----
class RetryMiddleware(Middleware)
⋮----
"""Middleware that implements automatic retry logic for failed requests.
    Retries requests that fail with transient errors, using exponential
    backoff to avoid overwhelming the server or external dependencies.
    Example:
        ```python
        from fastmcp.server.middleware.error_handling import RetryMiddleware
        # Retry up to 3 times with exponential backoff
        retry_middleware = RetryMiddleware(
            max_retries=3,
            retry_exceptions=(ConnectionError, TimeoutError)
        )
        mcp = FastMCP("MyServer")
        mcp.add_middleware(retry_middleware)
        ```
    """
⋮----
"""Initialize retry middleware.
        Args:
            max_retries: Maximum number of retry attempts
            base_delay: Initial delay between retries in seconds
            max_delay: Maximum delay between retries in seconds
            backoff_multiplier: Multiplier for exponential backoff
            retry_exceptions: Tuple of exception types that should trigger retries
            logger: Logger for retry attempts
        """
⋮----
def _should_retry(self, error: Exception) -> bool
⋮----
"""Determine if an error should trigger a retry."""
⋮----
def _calculate_delay(self, attempt: int) -> float
⋮----
"""Calculate delay for the given attempt number."""
delay = self.base_delay * (self.backoff_multiplier**attempt)
⋮----
async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any
⋮----
"""Implement retry logic for requests."""
last_error = None
⋮----
last_error = error
# Don't retry on the last attempt or if it's not a retryable error
⋮----
delay = self._calculate_delay(attempt)
⋮----
# Re-raise the last error if all retries failed

================
File: src/fastmcp/server/middleware/logging.py
================
"""Comprehensive logging middleware for FastMCP servers."""
⋮----
class LoggingMiddleware(Middleware)
⋮----
"""Middleware that provides comprehensive request and response logging.
    Logs all MCP messages with configurable detail levels. Useful for debugging,
    monitoring, and understanding server usage patterns.
    Example:
        ```python
        from fastmcp.server.middleware.logging import LoggingMiddleware
        import logging
        # Configure logging
        logging.basicConfig(level=logging.INFO)
        mcp = FastMCP("MyServer")
        mcp.add_middleware(LoggingMiddleware())
        ```
    """
⋮----
"""Initialize logging middleware.
        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.requests'
            log_level: Log level for messages (default: INFO)
            include_payloads: Whether to include message payloads in logs
            max_payload_length: Maximum length of payload to log (prevents huge logs)
            methods: List of methods to log. If None, logs all methods.
        """
⋮----
def _format_message(self, context: MiddlewareContext) -> str
⋮----
"""Format a message for logging."""
parts = [
⋮----
payload = json.dumps(context.message.__dict__, default=str)
⋮----
payload = payload[: self.max_payload_length] + "..."
⋮----
async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any
⋮----
"""Log all messages."""
message_info = self._format_message(context)
⋮----
result = await call_next(context)
⋮----
class StructuredLoggingMiddleware(Middleware)
⋮----
"""Middleware that provides structured JSON logging for better log analysis.
    Outputs structured logs that are easier to parse and analyze with log
    aggregation tools like ELK stack, Splunk, or cloud logging services.
    Example:
        ```python
        from fastmcp.server.middleware.logging import StructuredLoggingMiddleware
        import logging
        mcp = FastMCP("MyServer")
        mcp.add_middleware(StructuredLoggingMiddleware())
        ```
    """
⋮----
"""Initialize structured logging middleware.
        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.structured'
            log_level: Log level for messages (default: INFO)
            include_payloads: Whether to include message payloads in logs
            methods: List of methods to log. If None, logs all methods.
        """
⋮----
"""Create a structured log entry."""
entry = {
⋮----
"""Log structured message information."""
start_entry = self._create_log_entry(context, "request_start")
⋮----
success_entry = self._create_log_entry(
⋮----
error_entry = self._create_log_entry(

================
File: src/fastmcp/server/middleware/middleware.py
================
logger = logging.getLogger(__name__)
T = TypeVar("T")
R = TypeVar("R", covariant=True)
⋮----
@runtime_checkable
class CallNext(Protocol[T, R])
⋮----
def __call__(self, context: MiddlewareContext[T]) -> Awaitable[R]: ...
ServerResultT = TypeVar(
⋮----
@dataclass(kw_only=True)
class CallToolResult
⋮----
content: list[mt.Content]
isError: bool = False
⋮----
@dataclass(kw_only=True)
class ListToolsResult
⋮----
tools: dict[str, Tool]
⋮----
@dataclass(kw_only=True)
class ListResourcesResult
⋮----
resources: list[Resource]
⋮----
@dataclass(kw_only=True)
class ListResourceTemplatesResult
⋮----
resource_templates: list[ResourceTemplate]
⋮----
@dataclass(kw_only=True)
class ListPromptsResult
⋮----
prompts: list[Prompt]
⋮----
@runtime_checkable
class ServerResultProtocol(Protocol[ServerResultT])
⋮----
root: ServerResultT
⋮----
@dataclass(kw_only=True, frozen=True)
class MiddlewareContext(Generic[T])
⋮----
"""
    Unified context for all middleware operations.
    """
message: T
fastmcp_context: Context | None = None
# Common metadata
source: Literal["client", "server"] = "client"
type: Literal["request", "notification"] = "request"
method: str | None = None
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def copy(self, **kwargs: Any) -> MiddlewareContext[T]
⋮----
"""Create a wrapper that applies a single middleware to a context. The
    closure bakes in the middleware and call_next function, so it can be
    passed to other functions that expect a call_next function."""
async def wrapper(context: MiddlewareContext[T]) -> R
⋮----
class Middleware
⋮----
"""Base class for FastMCP middleware with dispatching hooks."""
⋮----
"""Main entry point that orchestrates the pipeline."""
handler_chain = await self._dispatch_handler(
⋮----
"""Builds a chain of handlers for a given message."""
handler = call_next
⋮----
handler = partial(self.on_call_tool, call_next=handler)
⋮----
handler = partial(self.on_read_resource, call_next=handler)
⋮----
handler = partial(self.on_get_prompt, call_next=handler)
⋮----
handler = partial(self.on_list_tools, call_next=handler)
⋮----
handler = partial(self.on_list_resources, call_next=handler)
⋮----
handler = partial(self.on_list_resource_templates, call_next=handler)
⋮----
handler = partial(self.on_list_prompts, call_next=handler)
⋮----
handler = partial(self.on_request, call_next=handler)
⋮----
handler = partial(self.on_notification, call_next=handler)
handler = partial(self.on_message, call_next=handler)

================
File: src/fastmcp/server/middleware/rate_limiting.py
================
"""Rate limiting middleware for protecting FastMCP servers from abuse."""
⋮----
class RateLimitError(McpError)
⋮----
"""Error raised when rate limit is exceeded."""
def __init__(self, message: str = "Rate limit exceeded")
class TokenBucketRateLimiter
⋮----
"""Token bucket implementation for rate limiting."""
def __init__(self, capacity: int, refill_rate: float)
⋮----
"""Initialize token bucket.
        Args:
            capacity: Maximum number of tokens in the bucket
            refill_rate: Tokens added per second
        """
⋮----
async def consume(self, tokens: int = 1) -> bool
⋮----
"""Try to consume tokens from the bucket.
        Args:
            tokens: Number of tokens to consume
        Returns:
            True if tokens were available and consumed, False otherwise
        """
⋮----
now = time.time()
elapsed = now - self.last_refill
# Add tokens based on elapsed time
⋮----
class SlidingWindowRateLimiter
⋮----
"""Sliding window rate limiter implementation."""
def __init__(self, max_requests: int, window_seconds: int)
⋮----
"""Initialize sliding window rate limiter.
        Args:
            max_requests: Maximum requests allowed in the time window
            window_seconds: Time window in seconds
        """
⋮----
async def is_allowed(self) -> bool
⋮----
"""Check if a request is allowed."""
⋮----
cutoff = now - self.window_seconds
# Remove old requests outside the window
⋮----
class RateLimitingMiddleware(Middleware)
⋮----
"""Middleware that implements rate limiting to prevent server abuse.
    Uses a token bucket algorithm by default, allowing for burst traffic
    while maintaining a sustainable long-term rate.
    Example:
        ```python
        from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware
        # Allow 10 requests per second with bursts up to 20
        rate_limiter = RateLimitingMiddleware(
            max_requests_per_second=10,
            burst_capacity=20
        )
        mcp = FastMCP("MyServer")
        mcp.add_middleware(rate_limiter)
        ```
    """
⋮----
"""Initialize rate limiting middleware.
        Args:
            max_requests_per_second: Sustained requests per second allowed
            burst_capacity: Maximum burst capacity. If None, defaults to 2x max_requests_per_second
            get_client_id: Function to extract client ID from context. If None, uses global limiting
            global_limit: If True, apply limit globally; if False, per-client
        """
⋮----
# Storage for rate limiters per client
⋮----
# Global rate limiter
⋮----
def _get_client_identifier(self, context: MiddlewareContext) -> str
⋮----
"""Get client identifier for rate limiting."""
⋮----
async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any
⋮----
"""Apply rate limiting to requests."""
⋮----
# Global rate limiting
allowed = await self.global_limiter.consume()
⋮----
# Per-client rate limiting
client_id = self._get_client_identifier(context)
limiter = self.limiters[client_id]
allowed = await limiter.consume()
⋮----
class SlidingWindowRateLimitingMiddleware(Middleware)
⋮----
"""Middleware that implements sliding window rate limiting.
    Uses a sliding window approach which provides more precise rate limiting
    but uses more memory to track individual request timestamps.
    Example:
        ```python
        from fastmcp.server.middleware.rate_limiting import SlidingWindowRateLimitingMiddleware
        # Allow 100 requests per minute
        rate_limiter = SlidingWindowRateLimitingMiddleware(
            max_requests=100,
            window_minutes=1
        )
        mcp = FastMCP("MyServer")
        mcp.add_middleware(rate_limiter)
        ```
    """
⋮----
"""Initialize sliding window rate limiting middleware.
        Args:
            max_requests: Maximum requests allowed in the time window
            window_minutes: Time window in minutes
            get_client_id: Function to extract client ID from context
        """
⋮----
"""Apply sliding window rate limiting to requests."""
⋮----
allowed = await limiter.is_allowed()

================
File: src/fastmcp/server/middleware/timing.py
================
"""Timing middleware for measuring and logging request performance."""
⋮----
class TimingMiddleware(Middleware)
⋮----
"""Middleware that logs the execution time of requests.
    Only measures and logs timing for request messages (not notifications).
    Provides insights into performance characteristics of your MCP server.
    Example:
        ```python
        from fastmcp.server.middleware.timing import TimingMiddleware
        mcp = FastMCP("MyServer")
        mcp.add_middleware(TimingMiddleware())
        # Now all requests will be timed and logged
        ```
    """
⋮----
"""Initialize timing middleware.
        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.timing'
            log_level: Log level for timing messages (default: INFO)
        """
⋮----
async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any
⋮----
"""Time request execution and log the results."""
method = context.method or "unknown"
start_time = time.perf_counter()
⋮----
result = await call_next(context)
duration_ms = (time.perf_counter() - start_time) * 1000
⋮----
class DetailedTimingMiddleware(Middleware)
⋮----
"""Enhanced timing middleware with per-operation breakdowns.
    Provides detailed timing information for different types of MCP operations,
    allowing you to identify performance bottlenecks in specific operations.
    Example:
        ```python
        from fastmcp.server.middleware.timing import DetailedTimingMiddleware
        import logging
        # Configure logging to see the output
        logging.basicConfig(level=logging.INFO)
        mcp = FastMCP("MyServer")
        mcp.add_middleware(DetailedTimingMiddleware())
        ```
    """
⋮----
"""Initialize detailed timing middleware.
        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.timing.detailed'
            log_level: Log level for timing messages (default: INFO)
        """
⋮----
"""Helper method to time any operation."""
⋮----
"""Time tool execution."""
tool_name = getattr(context.message, "name", "unknown")
⋮----
"""Time resource reading."""
resource_uri = getattr(context.message, "uri", "unknown")
⋮----
"""Time prompt retrieval."""
prompt_name = getattr(context.message, "name", "unknown")
⋮----
"""Time tool listing."""
⋮----
"""Time resource listing."""
⋮----
"""Time resource template listing."""
⋮----
"""Time prompt listing."""

================
File: src/fastmcp/server/__init__.py
================
__all__ = ["FastMCP", "Context"]

================
File: src/fastmcp/server/context.py
================
logger = get_logger(__name__)
T = TypeVar("T")
_current_context: ContextVar[Context | None] = ContextVar("context", default=None)
_flush_lock = asyncio.Lock()
⋮----
@contextmanager
def set_context(context: Context) -> Generator[Context, None, None]
⋮----
token = _current_context.set(context)
⋮----
@dataclass
class Context
⋮----
"""Context object providing access to MCP capabilities.
    This provides a cleaner interface to MCP's RequestContext functionality.
    It gets injected into tool and resource functions that request it via type hints.
    To use context in a tool function, add a parameter with the Context type annotation:
    ```python
    @server.tool
    def my_tool(x: int, ctx: Context) -> str:
        # Log messages to the client
        ctx.info(f"Processing {x}")
        ctx.debug("Debug info")
        ctx.warning("Warning message")
        ctx.error("Error message")
        # Report progress
        ctx.report_progress(50, 100, "Processing")
        # Access resources
        data = ctx.read_resource("resource://data")
        # Get request info
        request_id = ctx.request_id
        client_id = ctx.client_id
        return str(x)
    ```
    The context parameter name can be anything as long as it's annotated with Context.
    The context is optional - tools that don't need it can omit the parameter.
    """
def __init__(self, fastmcp: FastMCP)
⋮----
self._notification_queue: set[str] = set()  # Dedupe notifications
async def __aenter__(self) -> Context
⋮----
"""Enter the context manager and set this context as the current context."""
# Always set this context and save the token
token = _current_context.set(self)
⋮----
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None
⋮----
"""Exit the context manager and reset the most recent token."""
# Flush any remaining notifications before exiting
⋮----
token = self._tokens.pop()
⋮----
@property
    def request_context(self) -> RequestContext
⋮----
"""Access to the underlying request context.
        If called outside of a request context, this will raise a ValueError.
        """
⋮----
"""Report progress for the current operation.
        Args:
            progress: Current progress value e.g. 24
            total: Optional total value e.g. 100
        """
progress_token = (
⋮----
async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]
⋮----
"""Read a resource by URI.
        Args:
            uri: Resource URI to read
        Returns:
            The resource content as either text or bytes
        """
⋮----
"""Send a log message to the client.
        Args:
            message: Log message
            level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
                "alert", or "emergency". Default is "info".
            logger_name: Optional logger name
        """
⋮----
level = "info"
⋮----
@property
    def client_id(self) -> str | None
⋮----
"""Get the client ID if available."""
⋮----
@property
    def request_id(self) -> str
⋮----
"""Get the unique ID for this request."""
⋮----
@property
    def session_id(self) -> str | None
⋮----
"""Get the MCP session ID for HTTP transports.
        Returns the session ID that can be used as a key for session-based
        data storage (e.g., Redis) to share data between tool calls within
        the same client session.
        Returns:
            The session ID for HTTP transports (SSE, StreamableHTTP), or None
            for stdio and in-memory transports which don't use session IDs.
        Example:
            ```python
            @server.tool
            def store_data(data: dict, ctx: Context) -> str:
                if session_id := ctx.session_id:
                    redis_client.set(f"session:{session_id}:data", json.dumps(data))
                    return f"Data stored for session {session_id}"
                return "No session ID available (stdio/memory transport)"
            ```
        """
⋮----
headers = get_http_headers(include_all=True)
⋮----
# No HTTP context available (stdio/in-memory transport)
⋮----
@property
    def session(self) -> ServerSession
⋮----
"""Access to the underlying session for advanced usage."""
⋮----
# Convenience methods for common log levels
async def debug(self, message: str, logger_name: str | None = None) -> None
⋮----
"""Send a debug log message."""
⋮----
async def info(self, message: str, logger_name: str | None = None) -> None
⋮----
"""Send an info log message."""
⋮----
async def warning(self, message: str, logger_name: str | None = None) -> None
⋮----
"""Send a warning log message."""
⋮----
async def error(self, message: str, logger_name: str | None = None) -> None
⋮----
"""Send an error log message."""
⋮----
async def list_roots(self) -> list[Root]
⋮----
"""List the roots available to the server, as indicated by the client."""
result = await self.session.list_roots()
⋮----
async def send_tool_list_changed(self) -> None
⋮----
"""Send a tool list changed notification to the client."""
⋮----
async def send_resource_list_changed(self) -> None
⋮----
"""Send a resource list changed notification to the client."""
⋮----
async def send_prompt_list_changed(self) -> None
⋮----
"""Send a prompt list changed notification to the client."""
⋮----
"""
        Send a sampling request to the client and await the response.
        Call this method at any time to have the server request an LLM
        completion from the client. The client must be appropriately configured,
        or the request will error.
        """
⋮----
max_tokens = 512
⋮----
sampling_messages = [
⋮----
result: CreateMessageResult = await self.session.create_message(
⋮----
"""
        Send an elicitation request to the client and await the response.
        Call this method at any time to request additional information from
        the user through the client. The client must support elicitation,
        or the request will error.
        Note that the MCP protocol only supports simple object schemas with
        primitive types. You can provide a dataclass, TypedDict, or BaseModel to
        comply. If you provide a primitive type, an object schema with a single
        "value" field will be generated for the MCP interaction and
        automatically deconstructed into the primitive type upon response.
        Args:
            message: A human-readable message explaining what information is needed
            response_type: The type of the response, which should be a primitive
                type or dataclass or BaseModel. If it is a primitive type, an
                object schema with a single "value" field will be generated.
        """
⋮----
response_type = str  # type: ignore
# if the user provided a list of strings, treat it as a Literal
⋮----
# Convert list of options to Literal type and wrap
choice_literal = Literal[tuple(response_type)]  # type: ignore
response_type = ScalarElicitationType[choice_literal]  # type: ignore
# if the user provided a primitive scalar, wrap it in an object schema
⋮----
response_type = ScalarElicitationType[response_type]  # type: ignore
# if the user provided a Literal type, wrap it in an object schema
⋮----
# if the user provided an Enum type, wrap it in an object schema
⋮----
response_type = cast(type[T], response_type)
requested_schema = get_elicitation_schema(response_type)
result = await self.session.elicit(
⋮----
type_adapter = get_cached_typeadapter(response_type)
validated_data = cast(
⋮----
# This should never happen, but handle it just in case
⋮----
def get_http_request(self) -> Request
⋮----
"""Get the active starlette request."""
# Deprecated in 2.2.11
⋮----
def _queue_tool_list_changed(self) -> None
⋮----
"""Queue a tool list changed notification."""
⋮----
def _queue_resource_list_changed(self) -> None
⋮----
"""Queue a resource list changed notification."""
⋮----
def _queue_prompt_list_changed(self) -> None
⋮----
"""Queue a prompt list changed notification."""
⋮----
def _try_flush_notifications(self) -> None
⋮----
"""Synchronous method that attempts to flush notifications if we're in an async context."""
⋮----
# Check if we're in an async context
loop = asyncio.get_running_loop()
⋮----
# Schedule flush as a task (fire-and-forget)
⋮----
# No event loop - will flush later
⋮----
async def _flush_notifications(self) -> None
⋮----
"""Send all queued notifications."""
⋮----
# Don't let notification failures break the request
⋮----
"""
        Validates and converts user input for model_preferences into a ModelPreferences object.
        Args:
            model_preferences (ModelPreferences | str | list[str] | None):
                The model preferences to use. Accepts:
                - ModelPreferences (returns as-is)
                - str (single model hint)
                - list[str] (multiple model hints)
                - None (no preferences)
        Returns:
            ModelPreferences | None: The parsed ModelPreferences object, or None if not provided.
        Raises:
            ValueError: If the input is not a supported type or contains invalid values.
        """
⋮----
# Single model hint
⋮----
# List of model hints (strings)

================
File: src/fastmcp/server/dependencies.py
================
P = ParamSpec("P")
R = TypeVar("R")
__all__ = [
# --- Context ---
def get_context() -> Context
⋮----
context = _current_context.get()
⋮----
# --- HTTP Request ---
def get_http_request() -> Request
⋮----
request = _current_http_request.get()
⋮----
def get_http_headers(include_all: bool = False) -> dict[str, str]
⋮----
"""
    Extract headers from the current HTTP request if available.
    Never raises an exception, even if there is no active HTTP request (in which case
    an empty dict is returned).
    By default, strips problematic headers like `content-length` that cause issues if forwarded to downstream clients.
    If `include_all` is True, all headers are returned.
    """
⋮----
exclude_headers = set()
⋮----
exclude_headers = {
⋮----
# Proxy-related headers
⋮----
# (just in case)
⋮----
headers = {}
⋮----
request = get_http_request()
⋮----
lower_name = name.lower()

================
File: src/fastmcp/server/elicitation.py
================
__all__ = [
logger = get_logger(__name__)
T = TypeVar("T")
# we can't use the low-level AcceptedElicitation because it only works with BaseModels
class AcceptedElicitation(BaseModel, Generic[T])
⋮----
"""Result when user accepts the elicitation."""
action: Literal["accept"] = "accept"
data: T
⋮----
@dataclass
class ScalarElicitationType(Generic[T])
⋮----
value: T
def get_elicitation_schema(response_type: type[T]) -> dict[str, Any]
⋮----
"""Get the schema for an elicitation response.
    Args:
        response_type: The type of the response
    """
schema = get_cached_typeadapter(response_type).json_schema()
schema = compress_schema(schema)
# Validate the schema to ensure it follows MCP elicitation requirements
⋮----
def validate_elicitation_json_schema(schema: dict[str, Any]) -> None
⋮----
"""Validate that a JSON schema follows MCP elicitation requirements.
    This ensures the schema is compatible with MCP elicitation requirements:
    - Must be an object schema
    - Must only contain primitive field types (string, number, integer, boolean)
    - Must be flat (no nested objects or arrays of objects)
    - Allows const fields (for Literal types) and enum fields (for Enum types)
    - Only primitive types and their nullable variants are allowed
    Args:
        schema: The JSON schema to validate
    Raises:
        TypeError: If the schema doesn't meet MCP elicitation requirements
    """
ALLOWED_TYPES = {"string", "number", "integer", "boolean"}
# Check that the schema is an object
⋮----
properties = schema.get("properties", {})
⋮----
prop_type = prop_schema.get("type")
# Handle nullable types
⋮----
prop_type = [t for t in prop_type if t != "null"]
⋮----
prop_type = prop_type[0]
⋮----
continue  # Nullable with no other type is fine
# Handle const fields (Literal types)
⋮----
continue  # const fields are allowed regardless of type
# Handle enum fields (Enum types)
⋮----
continue  # enum fields are allowed regardless of type
# Handle references to definitions (like Enum types)
⋮----
# Get the referenced definition
ref_path = prop_schema["$ref"]
⋮----
def_name = ref_path[8:]  # Remove "#/$defs/" prefix
ref_def = schema.get("$defs", {}).get(def_name, {})
# If the referenced definition has an enum, it's allowed
⋮----
# If the referenced definition has a type that's allowed, it's allowed
ref_type = ref_def.get("type")
⋮----
# If we can't determine what the ref points to, reject it for safety
⋮----
# Handle union types (oneOf/anyOf)
⋮----
union_schemas = prop_schema.get("oneOf", []) + prop_schema.get("anyOf", [])
⋮----
# Allow const and enum in unions
⋮----
union_type = union_schema.get("type")
⋮----
# Check if it's a primitive type
⋮----
# Check for nested objects or arrays of objects (not allowed)
⋮----
items_schema = prop_schema.get("items", {})

================
File: src/fastmcp/server/http.py
================
logger = get_logger(__name__)
_current_http_request: ContextVar[Request | None] = ContextVar(
class StarletteWithLifespan(Starlette)
⋮----
@property
    def lifespan(self) -> Lifespan
⋮----
@contextmanager
def set_http_request(request: Request) -> Generator[Request, None, None]
⋮----
token = _current_http_request.set(request)
⋮----
class RequestContextMiddleware
⋮----
"""
    Middleware that stores each request in a ContextVar
    """
def __init__(self, app)
async def __call__(self, scope, receive, send)
⋮----
"""Set up authentication middleware and routes if auth is enabled.
    Args:
        auth: The OAuthProvider authorization server provider
    Returns:
        Tuple of (middleware, auth_routes, required_scopes)
    """
middleware: list[Middleware] = []
auth_routes: list[BaseRoute] = []
required_scopes: list[str] = []
middleware = [
required_scopes = auth.required_scopes or []
⋮----
"""Create a base Starlette app with common middleware and routes.
    Args:
        routes: List of routes to include in the app
        middleware: List of middleware to include in the app
        debug: Whether to enable debug mode
        lifespan: Optional lifespan manager for the app
    Returns:
        A Starlette application
    """
# Always add RequestContextMiddleware as the outermost middleware
⋮----
"""Return an instance of the SSE server app.
    Args:
        server: The FastMCP server instance
        message_path: Path for SSE messages
        sse_path: Path for SSE connections
        auth: Optional auth provider
        debug: Whether to enable debug mode
        routes: Optional list of custom routes
        middleware: Optional list of middleware
    Returns:
        A Starlette application with RequestContextMiddleware
    """
# Ensure the message_path ends with a trailing slash to avoid automatic redirects
⋮----
message_path = message_path + "/"
server_routes: list[BaseRoute] = []
server_middleware: list[Middleware] = []
# Set up SSE transport
sse = SseServerTransport(message_path)
# Create handler for SSE connections
async def handle_sse(scope: Scope, receive: Receive, send: Send) -> Response
# Get auth middleware and routes
# Add SSE routes with or without auth
⋮----
# Auth is enabled, wrap endpoints with RequireAuthMiddleware
⋮----
# No auth required
async def sse_endpoint(request: Request) -> Response
⋮----
return await handle_sse(request.scope, request.receive, request._send)  # type: ignore[reportPrivateUsage]
⋮----
# Add custom routes with lowest precedence
⋮----
# Add middleware
⋮----
# Create and return the app
app = create_base_app(
# Store the FastMCP server instance on the Starlette app state
⋮----
"""Return an instance of the StreamableHTTP server app.
    Args:
        server: The FastMCP server instance
        streamable_http_path: Path for StreamableHTTP connections
        event_store: Optional event store for session management
        auth: Optional auth provider
        json_response: Whether to use JSON response format
        stateless_http: Whether to use stateless mode (new transport per request)
        debug: Whether to enable debug mode
        routes: Optional list of custom routes
        middleware: Optional list of middleware
    Returns:
        A Starlette application with StreamableHTTP support
    """
⋮----
# Create session manager using the provided event store
session_manager = StreamableHTTPSessionManager(
# Create the ASGI handler
⋮----
new_error_message = (
# Raise a new RuntimeError that includes the original error's message
# for full context, but leads with the more helpful guidance.
⋮----
# Re-raise other RuntimeErrors if they don't match the specific message
⋮----
# Ensure the streamable_http_path ends with a trailing slash to avoid automatic redirects
⋮----
streamable_http_path = streamable_http_path + "/"
# Add StreamableHTTP routes with or without auth
⋮----
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
⋮----
# Create a lifespan manager to start and stop the session manager
⋮----
@asynccontextmanager
    async def lifespan(app: Starlette) -> AsyncGenerator[None, None]
# Create and return the app with lifespan

================
File: src/fastmcp/server/low_level.py
================
class LowLevelServer(_Server[LifespanResultT, RequestT])
⋮----
def __init__(self, *args, **kwargs)
⋮----
# FastMCP servers support notifications for all components
⋮----
# ensure we use the FastMCP notification options
⋮----
notification_options = self.notification_options

================
File: src/fastmcp/server/openapi.py
================
"""FastMCP server implementation for OpenAPI integration."""
⋮----
logger = get_logger(__name__)
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
def _slugify(text: str) -> str
⋮----
"""
    Convert text to a URL-friendly slug format that only contains lowercase
    letters, uppercase letters, numbers, and underscores.
    """
⋮----
# Replace spaces and common separators with underscores
slug = re.sub(r"[\s\-\.]+", "_", text)
# Remove non-alphanumeric characters except underscores
slug = re.sub(r"[^a-zA-Z0-9_]", "", slug)
# Remove multiple consecutive underscores
slug = re.sub(r"_+", "_", slug)
# Remove leading/trailing underscores
slug = slug.strip("_")
⋮----
# Type definitions for the mapping functions
RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
ComponentFn = Callable[
class MCPType(enum.Enum)
⋮----
"""Type of FastMCP component to create from a route.
    Enum values:
        TOOL: Convert the route to a callable Tool
        RESOURCE: Convert the route to a Resource (typically GET endpoints)
        RESOURCE_TEMPLATE: Convert the route to a ResourceTemplate (typically GET with path params)
        EXCLUDE: Exclude the route from being converted to any MCP component
        IGNORE: Deprecated, use EXCLUDE instead
    """
TOOL = "TOOL"
RESOURCE = "RESOURCE"
RESOURCE_TEMPLATE = "RESOURCE_TEMPLATE"
# PROMPT = "PROMPT"
EXCLUDE = "EXCLUDE"
# Keep RouteType as an alias to MCPType for backward compatibility
class RouteType(enum.Enum)
⋮----
"""
    Deprecated: Use MCPType instead.
    This enum is kept for backward compatibility and will be removed in a future version.
    """
⋮----
IGNORE = "IGNORE"
⋮----
@dataclass(kw_only=True)
class RouteMap
⋮----
"""Mapping configuration for HTTP routes to FastMCP component types."""
methods: list[HttpMethod] | Literal["*"] = field(default="*")
pattern: Pattern[str] | str = field(default=r".*")
route_type: RouteType | MCPType | None = field(default=None)
tags: set[str] = field(
mcp_type: MCPType | None = field(
mcp_tags: set[str] = field(
def __post_init__(self)
⋮----
"""Validate and process the route map after initialization."""
# Handle backward compatibility for route_type, deprecated in 2.5.0
⋮----
# Check for the deprecated IGNORE value
⋮----
# Convert from RouteType to MCPType if needed
⋮----
route_type_name = self.route_type.name
⋮----
route_type_name = "EXCLUDE"
⋮----
# Set route_type to match mcp_type for backward compatibility
⋮----
# Default route mapping: all routes become tools.
# Users can provide custom route_maps to override this behavior.
DEFAULT_ROUTE_MAPPINGS = [
⋮----
"""
    Determines the FastMCP component type based on the route and mappings.
    Args:
        route: HTTPRoute object
        mappings: List of RouteMap objects in priority order
    Returns:
        The RouteMap that matches the route, or a catchall "Tool" RouteMap if no match is found.
    """
# Check mappings in priority order (first match wins)
⋮----
# Check if the HTTP method matches
⋮----
# Handle both string patterns and compiled Pattern objects
⋮----
pattern_matches = route_map.pattern.search(route.path)
⋮----
pattern_matches = re.search(route_map.pattern, route.path)
⋮----
# Check if tags match (if specified)
# If route_map.tags is empty, tags are not matched
# If route_map.tags is non-empty, all tags must be present in route.tags (AND condition)
⋮----
route_tags_set = set(route.tags or [])
⋮----
# Tags don't match, continue to next mapping
⋮----
# We know mcp_type is not None here due to post_init validation
⋮----
# Default fallback
⋮----
class OpenAPITool(Tool)
⋮----
"""Tool implementation for OpenAPI endpoints."""
⋮----
def __repr__(self) -> str
⋮----
"""Custom representation to prevent recursion errors when printing."""
⋮----
async def run(self, arguments: dict[str, Any]) -> ToolResult
⋮----
"""Execute the HTTP request based on the route configuration."""
# Prepare URL
path = self._route.path
# Replace path parameters with values from kwargs
# Path parameters should never be None as they're typically required
# but we'll handle that case anyway
path_params = {
# Ensure all path parameters are provided
required_path_params = {
missing_params = required_path_params - path_params.keys()
⋮----
# Handle array path parameters with style 'simple' (comma-separated)
# In OpenAPI, 'simple' is the default style for path parameters
param_info = next(
⋮----
# Check if schema indicates an array type
schema = param_info.schema_
is_array = schema.get("type") == "array"
⋮----
# Format array values as comma-separated string
# This follows the OpenAPI 'simple' style (default for path)
⋮----
# Handle simple array types
path = path.replace(
⋮----
# Handle complex array types (containing objects/dicts)
⋮----
# Try to create a simple representation without Python syntax artifacts
formatted_parts = []
⋮----
# For objects, serialize key-value pairs
item_parts = []
⋮----
# Fallback for other complex types
⋮----
# Join parts with commas
formatted_value = ",".join(formatted_parts)
path = path.replace(f"{{{param_name}}}", formatted_value)
⋮----
# Fallback to string representation, but remove Python syntax artifacts
str_value = (
path = path.replace(f"{{{param_name}}}", str_value)
⋮----
# Default handling for non-array parameters or non-array schemas
path = path.replace(f"{{{param_name}}}", str(param_value))
# Prepare query parameters - filter out None and empty strings
query_params = {}
⋮----
param_value = arguments.get(p.name)
# Format array query parameters as comma-separated strings
# following OpenAPI form style (default for query parameters)
⋮----
# Get explode parameter from schema, default is True for query parameters
# If explode is True, the array is serialized as separate parameters
# If explode is False, the array is serialized as a comma-separated string
explode = p.schema_.get("explode", True)
⋮----
# When explode=True, we pass the array directly, which HTTPX will serialize
# as multiple parameters with the same name
⋮----
# For arrays of simple types (strings, numbers, etc.), join with commas
⋮----
# For complex types, try to create a simpler representation
⋮----
# Try to create a simple string representation
⋮----
# For objects, serialize key-value pairs
⋮----
# Fallback to string representation
⋮----
# Non-array parameters are passed as is
⋮----
# Prepare headers - fix typing by ensuring all values are strings
headers = {}
# Start with OpenAPI-defined header parameters
openapi_headers = {}
⋮----
# Add headers from the current MCP client HTTP request (these take precedence)
mcp_headers = get_http_headers()
⋮----
# Prepare request body
json_data = None
⋮----
# Extract body parameters, excluding path/query/header params that were already used
path_query_header_params = {
body_params = {
⋮----
json_data = body_params
# Execute the request
⋮----
response = await self._client.request(
# Raise for 4xx/5xx responses
⋮----
# Try to parse as JSON first
⋮----
result = response.json()
⋮----
result = {"result": result}
⋮----
# Handle HTTP errors (4xx, 5xx)
error_message = (
⋮----
error_data = e.response.json()
⋮----
# Handle request errors (connection, timeout, etc.)
⋮----
class OpenAPIResource(Resource)
⋮----
"""Resource implementation for OpenAPI endpoints."""
⋮----
uri=AnyUrl(uri),  # Convert string to AnyUrl
⋮----
async def read(self) -> str | bytes
⋮----
"""Fetch the resource data by making an HTTP request."""
⋮----
# Extract path parameters from the URI if present
⋮----
resource_uri = str(self.uri)
# If this is a templated resource, extract path parameters from the URI
⋮----
# Extract the resource ID from the URI (the last part after the last slash)
parts = resource_uri.split("/")
⋮----
# Find all path parameters in the route path
path_params = {}
# Find the path parameter names from the route path
param_matches = re.findall(r"\{([^}]+)\}", path)
⋮----
# Reverse sorting from creation order (traversal is backwards)
⋮----
# Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
expected_param_count = len(parts) - 1
# Map parameters from the end of the URI to the parameters in the path
# Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
⋮----
# Ensure we don't use resource identifier as parameter
⋮----
# Get values from the end of parts
param_value = parts[-1 - i]
⋮----
# Replace path parameters with their values
⋮----
# Filter any query parameters - get query parameters and filter out None/empty values
⋮----
value = getattr(self, f"_{param.name}")
⋮----
# Prepare headers from MCP client request if available
⋮----
# Determine content type and return appropriate format
content_type = response.headers.get("content-type", "").lower()
⋮----
class OpenAPIResourceTemplate(ResourceTemplate)
⋮----
"""Resource template implementation for OpenAPI endpoints."""
⋮----
"""Create a resource with the given parameters."""
# Generate a URI for this resource instance
uri_parts = []
⋮----
# Create and return a resource
⋮----
class FastMCPOpenAPI(FastMCP)
⋮----
"""
    FastMCP server implementation that creates components from an OpenAPI schema.
    This class parses an OpenAPI specification and creates appropriate FastMCP components
    (Tools, Resources, ResourceTemplates) based on route mappings.
    Example:
        ```python
        from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
        import httpx
        # Define custom route mappings
        custom_mappings = [
            # Map all user-related endpoints to ResourceTemplate
            RouteMap(
                methods=["GET", "POST", "PATCH"],
                pattern=r".*/users/.*",
                mcp_type=MCPType.RESOURCE_TEMPLATE
            ),
            # Map all analytics endpoints to Tool
            RouteMap(
                methods=["GET"],
                pattern=r".*/analytics/.*",
                mcp_type=MCPType.TOOL
            ),
        ]
        # Create server with custom mappings and route mapper
        server = FastMCPOpenAPI(
            openapi_spec=spec,
            client=httpx.AsyncClient(),
            name="API Server",
            route_maps=custom_mappings,
        )
        ```
    """
⋮----
"""
        Initialize a FastMCP server from an OpenAPI schema.
        Args:
            openapi_spec: OpenAPI schema as a dictionary or file path
            client: httpx AsyncClient for making HTTP requests
            name: Optional name for the server
            route_maps: Optional list of RouteMap objects defining route mappings
            route_map_fn: Optional callable for advanced route type mapping.
                Receives (route, mcp_type) and returns MCPType or None.
                Called on every route, including excluded ones.
            mcp_component_fn: Optional callable for component customization.
                Receives (route, component) and can modify the component in-place.
                Called on every created component.
            mcp_names: Optional dictionary mapping operationId to desired component names.
                If an operationId is not in the dictionary, falls back to using the
                operationId up to the first double underscore. If no operationId exists,
                falls back to slugified summary or path-based naming.
                All names are truncated to 56 characters maximum.
            tags: Optional set of tags to add to all components. Components always receive any tags
                from the route.
            timeout: Optional timeout (in seconds) for all requests
            **settings: Additional settings for FastMCP
        """
⋮----
# Keep track of names to detect collisions
⋮----
http_routes = openapi.parse_openapi_to_http_routes(openapi_spec)
# Process routes
route_maps = (route_maps or []) + DEFAULT_ROUTE_MAPPINGS
⋮----
# Determine route type based on mappings or default rules
route_map = _determine_route_type(route, route_maps)
# TODO: remove this once RouteType is removed and mcp_type is typed as MCPType without | None
⋮----
route_type = route_map.mcp_type
# Call route_map_fn if provided
⋮----
result = route_map_fn(route, route_type)
⋮----
route_type = result
⋮----
# Generate a default name from the route
component_name = self._generate_default_name(route, mcp_names)
route_tags = set(route.tags) | route_map.mcp_tags | (tags or set())
⋮----
"""Generate a default name from the route using the configured strategy."""
name = ""
mcp_names_map = mcp_names_map or {}
# First check if there's a custom mapping for this operationId
⋮----
name = mcp_names_map[route.operation_id]
⋮----
# If there's a double underscore in the operationId, use the first part
name = route.operation_id.split("__")[0]
⋮----
name = route.summary or f"{route.method}_{route.path}"
name = _slugify(name)
# Truncate to 56 characters maximum
⋮----
name = name[:56]
⋮----
"""
        Ensure the name is unique within its component type by appending numbers if needed.
        Args:
            name: The proposed name
            component_type: The type of component ("tools", "resources", or "templates")
        Returns:
            str: A unique name for the component
        """
# Check if the name is already used
⋮----
# Create the new name
new_name = f"{name}_{self._used_names[component_type][name]}"
⋮----
"""Creates and registers an OpenAPITool with enhanced description."""
combined_schema = _combine_schemas(route)
# Get a unique tool name
tool_name = self._get_unique_name(name, "tool")
base_description = (
# Format enhanced description with parameters and request body
enhanced_description = format_description_with_responses(
tool = OpenAPITool(
# Call component_fn if provided
⋮----
# Register the tool by directly assigning to the tools dictionary
⋮----
"""Creates and registers an OpenAPIResource with enhanced description."""
# Get a unique resource name
resource_name = self._get_unique_name(name, "resource")
resource_uri = f"resource://{resource_name}"
⋮----
resource = OpenAPIResource(
⋮----
# Register the resource by directly assigning to the resources dictionary
⋮----
"""Creates and registers an OpenAPIResourceTemplate with enhanced description."""
# Get a unique template name
template_name = self._get_unique_name(name, "resource_template")
path_params = [p.name for p in route.parameters if p.location == "path"]
path_params.sort()  # Sort for consistent URIs
uri_template_str = f"resource://{template_name}"
⋮----
template_params_schema = {
template = OpenAPIResourceTemplate(
⋮----
# Register the template by directly assigning to the templates dictionary

================
File: src/fastmcp/server/proxy.py
================
logger = get_logger(__name__)
class ProxyToolManager(ToolManager)
⋮----
"""A ToolManager that sources its tools from a remote client in addition to local and mounted tools."""
def __init__(self, client: Client, **kwargs)
async def get_tools(self) -> dict[str, Tool]
⋮----
"""Gets the unfiltered tool inventory including local, mounted, and proxy tools."""
# First get local and mounted tools from parent
all_tools = await super().get_tools()
# Then add proxy tools, but don't overwrite existing ones
⋮----
client_tools = await self.client.list_tools()
⋮----
pass  # No tools available from proxy
⋮----
async def list_tools(self) -> list[Tool]
⋮----
"""Gets the filtered list of tools including local, mounted, and proxy tools."""
tools_dict = await self.get_tools()
⋮----
async def call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult
⋮----
"""Calls a tool, trying local/mounted first, then proxy if not found."""
⋮----
# First try local and mounted tools
⋮----
# If not found locally, try proxy
⋮----
result = await self.client.call_tool(key, arguments)
⋮----
class ProxyResourceManager(ResourceManager)
⋮----
"""A ResourceManager that sources its resources from a remote client in addition to local and mounted resources."""
⋮----
async def get_resources(self) -> dict[str, Resource]
⋮----
"""Gets the unfiltered resource inventory including local, mounted, and proxy resources."""
# First get local and mounted resources from parent
all_resources = await super().get_resources()
# Then add proxy resources, but don't overwrite existing ones
⋮----
client_resources = await self.client.list_resources()
⋮----
pass  # No resources available from proxy
⋮----
async def get_resource_templates(self) -> dict[str, ResourceTemplate]
⋮----
"""Gets the unfiltered template inventory including local, mounted, and proxy templates."""
# First get local and mounted templates from parent
all_templates = await super().get_resource_templates()
# Then add proxy templates, but don't overwrite existing ones
⋮----
client_templates = await self.client.list_resource_templates()
⋮----
pass  # No templates available from proxy
⋮----
async def list_resources(self) -> list[Resource]
⋮----
"""Gets the filtered list of resources including local, mounted, and proxy resources."""
resources_dict = await self.get_resources()
⋮----
async def list_resource_templates(self) -> list[ResourceTemplate]
⋮----
"""Gets the filtered list of templates including local, mounted, and proxy templates."""
templates_dict = await self.get_resource_templates()
⋮----
async def read_resource(self, uri: AnyUrl | str) -> str | bytes
⋮----
"""Reads a resource, trying local/mounted first, then proxy if not found."""
⋮----
# First try local and mounted resources
⋮----
result = await self.client.read_resource(uri)
⋮----
class ProxyPromptManager(PromptManager)
⋮----
"""A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts."""
⋮----
async def get_prompts(self) -> dict[str, Prompt]
⋮----
"""Gets the unfiltered prompt inventory including local, mounted, and proxy prompts."""
# First get local and mounted prompts from parent
all_prompts = await super().get_prompts()
# Then add proxy prompts, but don't overwrite existing ones
⋮----
client_prompts = await self.client.list_prompts()
⋮----
pass  # No prompts available from proxy
⋮----
async def list_prompts(self) -> list[Prompt]
⋮----
"""Gets the filtered list of prompts including local, mounted, and proxy prompts."""
prompts_dict = await self.get_prompts()
⋮----
"""Renders a prompt, trying local/mounted first, then proxy if not found."""
⋮----
# First try local and mounted prompts
⋮----
result = await self.client.get_prompt(name, arguments)
⋮----
class ProxyTool(Tool)
⋮----
"""
    A Tool that represents and executes a tool on a remote server.
    """
⋮----
@classmethod
    def from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
⋮----
"""Factory method to create a ProxyTool from a raw MCP tool schema."""
⋮----
"""Executes the tool by making a call through the client."""
# This is where the remote execution logic lives.
⋮----
result = await self._client.call_tool_mcp(
⋮----
class ProxyResource(Resource)
⋮----
"""
    A Resource that represents and reads a resource from a remote server.
    """
_client: Client
_value: str | bytes | None = None
def __init__(self, client: Client, *, _value: str | bytes | None = None, **kwargs)
⋮----
"""Factory method to create a ProxyResource from a raw MCP resource schema."""
⋮----
async def read(self) -> str | bytes
⋮----
"""Read the resource content from the remote server."""
⋮----
result = await self._client.read_resource(self.uri)
⋮----
class ProxyTemplate(ResourceTemplate)
⋮----
"""
    A ResourceTemplate that represents and creates resources from a remote server template.
    """
⋮----
"""Factory method to create a ProxyTemplate from a raw MCP template schema."""
⋮----
parameters={},  # Remote templates don't have local parameters
⋮----
"""Create a resource from the template by calling the remote server."""
# don't use the provided uri, because it may not be the same as the
# uri_template on the remote server.
# quote params to ensure they are valid for the uri_template
parameterized_uri = self.uri_template.format(
⋮----
result = await self._client.read_resource(parameterized_uri)
⋮----
value = result[0].text
⋮----
value = result[0].blob
⋮----
class ProxyPrompt(Prompt)
⋮----
"""
    A Prompt that represents and renders a prompt from a remote server.
    """
⋮----
"""Factory method to create a ProxyPrompt from a raw MCP prompt schema."""
arguments = [
⋮----
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]
⋮----
"""Render the prompt by making a call through the client."""
⋮----
result = await self._client.get_prompt(self.name, arguments)
⋮----
class FastMCPProxy(FastMCP)
⋮----
"""
    A FastMCP server that acts as a proxy to a remote MCP-compliant server.
    It uses specialized managers that fulfill requests via an HTTP client.
    """
⋮----
"""
        Initializes the proxy server.
        Args:
            client: The FastMCP client connected to the backend server.
            **kwargs: Additional settings for the FastMCP server.
        """
⋮----
# Replace the default managers with our specialized proxy managers.

================
File: src/fastmcp/server/server.py
================
"""FastMCP - A more ergonomic interface for MCP servers."""
⋮----
logger = get_logger(__name__)
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
Transport = Literal["stdio", "http", "sse", "streamable-http"]
# Compiled URI parsing regex to split a URI into protocol and path components
URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
⋮----
@asynccontextmanager
async def default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any]
⋮----
"""Default lifespan context manager that does nothing.
    Args:
        server: The server instance this lifespan is managing
    Returns:
        An empty context object
    """
⋮----
context = await stack.enter_async_context(lifespan(app))
⋮----
class FastMCP(Generic[LifespanResultT])
⋮----
# ---
⋮----
# --- The following arguments are DEPRECATED ---
⋮----
lifespan = default_lifespan
⋮----
auth = EnvBearerAuthProvider()
⋮----
tool = Tool.from_function(tool, serializer=self._tool_serializer)
⋮----
# Set up MCP protocol handlers
⋮----
# handle deprecated settings
⋮----
def __repr__(self) -> str
⋮----
"""Handle deprecated settings. Deprecated in 2.8.0."""
deprecated_settings: dict[str, Any] = {}
⋮----
# Deprecated in 2.8.0
⋮----
combined_settings = fastmcp.settings.model_dump() | deprecated_settings
⋮----
@property
    def settings(self) -> Settings
⋮----
# Deprecated in 2.8.0
⋮----
@property
    def name(self) -> str
⋮----
@property
    def instructions(self) -> str | None
⋮----
"""Run the FastMCP server asynchronously.
        Args:
            transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
        """
⋮----
transport = "stdio"
⋮----
"""Run the FastMCP server. Note this is a synchronous function.
        Args:
            transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
        """
⋮----
def _setup_handlers(self) -> None
⋮----
"""Set up core MCP protocol handlers."""
⋮----
"""Builds and executes the middleware chain."""
chain = call_next
⋮----
chain = partial(mw, call_next=chain)
⋮----
def add_middleware(self, middleware: Middleware) -> None
async def get_tools(self) -> dict[str, Tool]
⋮----
"""Get all registered tools, indexed by registered key."""
⋮----
async def get_tool(self, key: str) -> Tool
⋮----
tools = await self.get_tools()
⋮----
async def get_resources(self) -> dict[str, Resource]
⋮----
"""Get all registered resources, indexed by registered key."""
⋮----
async def get_resource(self, key: str) -> Resource
⋮----
resources = await self.get_resources()
⋮----
async def get_resource_templates(self) -> dict[str, ResourceTemplate]
⋮----
"""Get all registered resource templates, indexed by registered key."""
⋮----
async def get_resource_template(self, key: str) -> ResourceTemplate
⋮----
"""Get a registered resource template by key."""
templates = await self.get_resource_templates()
⋮----
async def get_prompts(self) -> dict[str, Prompt]
⋮----
"""
        List all available prompts.
        """
⋮----
async def get_prompt(self, key: str) -> Prompt
⋮----
prompts = await self.get_prompts()
⋮----
"""
        Decorator to register a custom HTTP route on the FastMCP server.
        Allows adding arbitrary HTTP endpoints outside the standard MCP protocol,
        which can be useful for OAuth callbacks, health checks, or admin APIs.
        The handler function must be an async function that accepts a Starlette
        Request and returns a Response.
        Args:
            path: URL path for the route (e.g., "/oauth/callback")
            methods: List of HTTP methods to support (e.g., ["GET", "POST"])
            name: Optional name for the route (to reference this route with
                Starlette's reverse URL lookup feature)
            include_in_schema: Whether to include in OpenAPI schema, defaults to True
        Example:
            Register a custom HTTP route for a health check endpoint:
            ```python
            @server.custom_route("/health", methods=["GET"])
            async def health_check(request: Request) -> Response:
                return JSONResponse({"status": "ok"})
            ```
        """
⋮----
async def _mcp_list_tools(self) -> list[MCPTool]
⋮----
tools = await self._list_tools()
⋮----
async def _list_tools(self) -> list[Tool]
⋮----
"""
        List all available tools, in the format expected by the low-level MCP
        server.
        """
⋮----
tools = await self._tool_manager.list_tools()  # type: ignore[reportPrivateUsage]
mcp_tools: list[Tool] = []
⋮----
# Create the middleware context.
mw_context = MiddlewareContext(
# Apply the middleware chain.
⋮----
async def _mcp_list_resources(self) -> list[MCPResource]
⋮----
resources = await self._list_resources()
⋮----
async def _list_resources(self) -> list[Resource]
⋮----
"""
        List all available resources, in the format expected by the low-level MCP
        server.
        """
⋮----
resources = await self._resource_manager.list_resources()  # type: ignore[reportPrivateUsage]
mcp_resources: list[Resource] = []
⋮----
message={},  # List resources doesn't have parameters
⋮----
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]
⋮----
templates = await self._list_resource_templates()
⋮----
async def _list_resource_templates(self) -> list[ResourceTemplate]
⋮----
"""
        List all available resource templates, in the format expected by the low-level MCP
        server.
        """
⋮----
templates = await self._resource_manager.list_resource_templates()
mcp_templates: list[ResourceTemplate] = []
⋮----
message={},  # List resource templates doesn't have parameters
⋮----
async def _mcp_list_prompts(self) -> list[MCPPrompt]
⋮----
prompts = await self._list_prompts()
⋮----
async def _list_prompts(self) -> list[Prompt]
⋮----
"""
        List all available prompts, in the format expected by the low-level MCP
        server.
        """
⋮----
prompts = await self._prompt_manager.list_prompts()  # type: ignore[reportPrivateUsage]
mcp_prompts: list[Prompt] = []
⋮----
"""
        Handle MCP 'callTool' requests.
        Delegates to _call_tool, which should be overridden by FastMCP subclasses.
        Args:
            key: The name of the tool to call
            arguments: Arguments to pass to the tool
        Returns:
            List of MCP Content objects containing the tool results
        """
⋮----
result = await self._call_tool(key, arguments)
⋮----
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult
⋮----
"""
        Applies this server's middleware and delegates the filtered call to the manager.
        """
⋮----
tool = await self._tool_manager.get_tool(context.message.name)
⋮----
async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]
⋮----
"""
        Handle MCP 'readResource' requests.
        Delegates to _read_resource, which should be overridden by FastMCP subclasses.
        """
⋮----
# convert to NotFoundError to avoid leaking resource presence
⋮----
# standardize NotFound message
⋮----
async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]
⋮----
resource = await self._resource_manager.get_resource(context.message.uri)
⋮----
content = await self._resource_manager.read_resource(context.message.uri)
⋮----
# Convert string URI to AnyUrl if needed
⋮----
uri_param = AnyUrl(uri)
⋮----
uri_param = uri
⋮----
"""
        Handle MCP 'getPrompt' requests.
        Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
        """
⋮----
# convert to NotFoundError to avoid leaking prompt presence
⋮----
prompt = await self._prompt_manager.get_prompt(context.message.name)
⋮----
def add_tool(self, tool: Tool) -> None
⋮----
"""Add a tool to the server.
        The tool function can optionally request a Context object by adding a parameter
        with the Context type annotation. See the @tool decorator for examples.
        Args:
            tool: The Tool instance to register
        """
⋮----
# Send notification if we're in a request context
⋮----
context = get_context()
context._queue_tool_list_changed()  # type: ignore[private-use]
⋮----
pass  # No context available
def remove_tool(self, name: str) -> None
⋮----
"""Remove a tool from the server.
        Args:
            name: The name of the tool to remove
        Raises:
            NotFoundError: If the tool is not found
        """
⋮----
"""Decorator to register a tool.
        Tools can optionally request a Context object by adding a parameter with the
        Context type annotation. The context provides access to MCP capabilities like
        logging, progress reporting, and resource access.
        This decorator supports multiple calling patterns:
        - @server.tool (without parentheses)
        - @server.tool (with empty parentheses)
        - @server.tool("custom_name") (with name as first argument)
        - @server.tool(name="custom_name") (with name as keyword argument)
        - server.tool(function, name="custom_name") (direct function call)
        Args:
            name_or_fn: Either a function (when used as @tool), a string name, or None
            name: Optional name for the tool (keyword-only, alternative to name_or_fn)
            description: Optional description of what the tool does
            tags: Optional set of tags for categorizing the tool
            output_schema: Optional JSON schema for the tool's output
            annotations: Optional annotations about the tool's behavior
            exclude_args: Optional list of argument names to exclude from the tool schema
            enabled: Optional boolean to enable or disable the tool
        Examples:
            Register a tool with a custom name:
            ```python
            @server.tool
            def my_tool(x: int) -> str:
                return str(x)
            # Register a tool with a custom name
            @server.tool
            def my_tool(x: int) -> str:
                return str(x)
            @server.tool("custom_name")
            def my_tool(x: int) -> str:
                return str(x)
            @server.tool(name="custom_name")
            def my_tool(x: int) -> str:
                return str(x)
            # Direct function call
            server.tool(my_function, name="custom_name")
            ```
        """
⋮----
annotations = ToolAnnotations(**annotations)
⋮----
# Determine the actual name and function based on the calling pattern
⋮----
# Case 1: @tool (without parens) - function passed directly
# Case 2: direct call like tool(fn, name="something")
fn = name_or_fn
tool_name = name  # Use keyword name if provided, otherwise None
# Register the tool immediately and return the tool object
tool = Tool.from_function(
⋮----
# Case 3: @tool("custom_name") - name passed as first argument
⋮----
tool_name = name_or_fn
⋮----
# Case 4: @tool or @tool(name="something") - use keyword name
tool_name = name
⋮----
# Return partial for cases where we need to wait for the function
⋮----
def add_resource(self, resource: Resource) -> None
⋮----
"""Add a resource to the server.
        Args:
            resource: A Resource instance to add
        """
⋮----
context._queue_resource_list_changed()  # type: ignore[private-use]
⋮----
def add_template(self, template: ResourceTemplate) -> None
⋮----
"""Add a resource template to the server.
        Args:
            template: A ResourceTemplate instance to add
        """
⋮----
"""Add a resource or template to the server from a function.
        If the URI contains parameters (e.g. "resource://{param}") or the function
        has parameters, it will be registered as a template resource.
        Args:
            fn: The function to register as a resource
            uri: The URI for the resource
            name: Optional name for the resource
            description: Optional description of the resource
            mime_type: Optional MIME type for the resource
            tags: Optional set of tags for categorizing the resource
        """
# deprecated since 2.7.0
⋮----
"""Decorator to register a function as a resource.
        The function will be called when the resource is read to generate its content.
        The function can return:
        - str for text content
        - bytes for binary content
        - other types will be converted to JSON
        Resources can optionally request a Context object by adding a parameter with the
        Context type annotation. The context provides access to MCP capabilities like
        logging, progress reporting, and session information.
        If the URI contains parameters (e.g. "resource://{param}") or the function
        has parameters, it will be registered as a template resource.
        Args:
            uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}")
            name: Optional name for the resource
            description: Optional description of the resource
            mime_type: Optional MIME type for the resource
            tags: Optional set of tags for categorizing the resource
            enabled: Optional boolean to enable or disable the resource
        Examples:
            Register a resource with a custom name:
            ```python
            @server.resource("resource://my-resource")
            def get_data() -> str:
                return "Hello, world!"
            @server.resource("resource://my-resource")
            async get_data() -> str:
                data = await fetch_data()
                return f"Hello, world! {data}"
            @server.resource("resource://{city}/weather")
            def get_weather(city: str) -> str:
                return f"Weather for {city}"
            @server.resource("resource://{city}/weather")
            def get_weather_with_context(city: str, ctx: Context) -> str:
                ctx.info(f"Fetching weather for {city}")
                return f"Weather for {city}"
            @server.resource("resource://{city}/weather")
            async def get_weather(city: str) -> str:
                data = await fetch_weather(city)
                return f"Weather for {city}: {data}"
            ```
        """
# Check if user passed function directly instead of calling decorator
⋮----
def decorator(fn: AnyFunction) -> Resource | ResourceTemplate
⋮----
if isinstance(fn, classmethod):  # type: ignore[reportUnnecessaryIsInstance]
⋮----
# Check if this should be a template
has_uri_params = "{" in uri and "}" in uri
# check if the function has any parameters (other than injected context)
has_func_params = any(
⋮----
template = ResourceTemplate.from_function(
⋮----
resource = Resource.from_function(
⋮----
def add_prompt(self, prompt: Prompt) -> None
⋮----
"""Add a prompt to the server.
        Args:
            prompt: A Prompt instance to add
        """
⋮----
context._queue_prompt_list_changed()  # type: ignore[private-use]
⋮----
"""Decorator to register a prompt.
        Prompts can optionally request a Context object by adding a parameter with the
        Context type annotation. The context provides access to MCP capabilities like
        logging, progress reporting, and session information.
        This decorator supports multiple calling patterns:
        - @server.prompt (without parentheses)
        - @server.prompt() (with empty parentheses)
        - @server.prompt("custom_name") (with name as first argument)
        - @server.prompt(name="custom_name") (with name as keyword argument)
        - server.prompt(function, name="custom_name") (direct function call)
        Args:
            name_or_fn: Either a function (when used as @prompt), a string name, or None
            name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
            description: Optional description of what the prompt does
            tags: Optional set of tags for categorizing the prompt
            enabled: Optional boolean to enable or disable the prompt
        Examples:
            ```python
            @server.prompt
            def analyze_table(table_name: str) -> list[Message]:
                schema = read_table_schema(table_name)
                return [
                    {
                        "role": "user",
                        "content": f"Analyze this schema:\n{schema}"
                    }
                ]
            @server.prompt()
            def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
                ctx.info(f"Analyzing table {table_name}")
                schema = read_table_schema(table_name)
                return [
                    {
                        "role": "user",
                        "content": f"Analyze this schema:\n{schema}"
                    }
                ]
            @server.prompt("custom_name")
            def analyze_file(path: str) -> list[Message]:
                content = await read_file(path)
                return [
                    {
                        "role": "user",
                        "content": {
                            "type": "resource",
                            "resource": {
                                "uri": f"file://{path}",
                                "text": content
                            }
                        }
                    }
                ]
            @server.prompt(name="custom_name")
            def another_prompt(data: str) -> list[Message]:
                return [{"role": "user", "content": data}]
            # Direct function call
            server.prompt(my_function, name="custom_name")
            ```
        """
⋮----
# Case 1: @prompt (without parens) - function passed directly as decorator
# Case 2: direct call like prompt(fn, name="something")
⋮----
prompt_name = name  # Use keyword name if provided, otherwise None
# Register the prompt immediately
prompt = Prompt.from_function(
⋮----
# Case 3: @prompt("custom_name") - name passed as first argument
⋮----
prompt_name = name_or_fn
⋮----
# Case 4: @prompt() or @prompt(name="something") - use keyword name
prompt_name = name
⋮----
async def run_stdio_async(self) -> None
⋮----
"""Run the server using stdio transport."""
⋮----
"""Run the server using HTTP transport.
        Args:
            transport: Transport protocol to use - either "streamable-http" (default) or "sse"
            host: Host address to bind to (defaults to settings.host)
            port: Port to bind to (defaults to settings.port)
            log_level: Log level for the server (defaults to settings.log_level)
            path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
            uvicorn_config: Additional configuration for the Uvicorn server
        """
host = host or self._deprecated_settings.host
port = port or self._deprecated_settings.port
default_log_level_to_use = (
app = self.http_app(path=path, transport=transport, middleware=middleware)
_uvicorn_config_from_user = uvicorn_config or {}
config_kwargs: dict[str, Any] = {
⋮----
config = uvicorn.Config(app, host=host, port=port, **config_kwargs)
server = uvicorn.Server(config)
path = app.state.path.lstrip("/")  # type: ignore
⋮----
"""Run the server using SSE transport."""
# Deprecated since 2.3.2
⋮----
"""
        Create a Starlette app for the SSE server.
        Args:
            path: The path to the SSE endpoint
            message_path: The path to the message endpoint
            middleware: A list of middleware to apply to the app
        """
⋮----
"""
        Create a Starlette app for the StreamableHTTP server.
        Args:
            path: The path to the StreamableHTTP endpoint
            middleware: A list of middleware to apply to the app
        """
⋮----
"""Create a Starlette app using the specified HTTP transport.
        Args:
            path: The path for the HTTP endpoint
            middleware: A list of middleware to apply to the app
            transport: Transport protocol to use - either "streamable-http" (default) or "sse"
        Returns:
            A Starlette application configured with the specified transport
        """
⋮----
"""Mount another FastMCP server on this server with an optional prefix.
        Unlike importing (with import_server), mounting establishes a dynamic connection
        between servers. When a client interacts with a mounted server's objects through
        the parent server, requests are forwarded to the mounted server in real-time.
        This means changes to the mounted server are immediately reflected when accessed
        through the parent.
        When a server is mounted with a prefix:
        - Tools from the mounted server are accessible with prefixed names.
          Example: If server has a tool named "get_weather", it will be available as "prefix_get_weather".
        - Resources are accessible with prefixed URIs.
          Example: If server has a resource with URI "weather://forecast", it will be available as
          "weather://prefix/forecast".
        - Templates are accessible with prefixed URI templates.
          Example: If server has a template with URI "weather://location/{id}", it will be available
          as "weather://prefix/location/{id}".
        - Prompts are accessible with prefixed names.
          Example: If server has a prompt named "weather_prompt", it will be available as
          "prefix_weather_prompt".
        When a server is mounted without a prefix (prefix=None), its tools, resources, templates,
        and prompts are accessible with their original names. Multiple servers can be mounted
        without prefixes, and they will be tried in order until a match is found.
        There are two modes for mounting servers:
        1. Direct mounting (default when server has no custom lifespan): The parent server
           directly accesses the mounted server's objects in-memory for better performance.
           In this mode, no client lifecycle events occur on the mounted server, including
           lifespan execution.
        2. Proxy mounting (default when server has a custom lifespan): The parent server
           treats the mounted server as a separate entity and communicates with it via a
           Client transport. This preserves all client-facing behaviors, including lifespan
           execution, but with slightly higher overhead.
        Args:
            server: The FastMCP server to mount.
            prefix: Optional prefix to use for the mounted server's objects. If None,
                the server's objects are accessible with their original names.
            as_proxy: Whether to treat the mounted server as a proxy. If None (default),
                automatically determined based on whether the server has a custom lifespan
                (True if it has a custom lifespan, False otherwise).
            tool_separator: Deprecated. Separator character for tool names.
            resource_separator: Deprecated. Separator character for resource URIs.
            prompt_separator: Deprecated. Separator character for prompt names.
        """
⋮----
# Deprecated since 2.9.0
# Prior to 2.9.0, the first positional argument was the prefix and the
# second was the server. Here we swap them if needed now that the prefix
# is optional.
⋮----
# Deprecated since 2.4.0
⋮----
# if as_proxy is not specified and the server has a custom lifespan,
# we should treat it as a proxy
⋮----
as_proxy = server._has_lifespan
⋮----
server = FastMCPProxy(Client(transport=FastMCPTransport(server)))
# Delegate mounting to all three managers
mounted_server = MountedServer(
⋮----
"""
        Import the MCP objects from another FastMCP server into this one,
        optionally with a given prefix.
        Note that when a server is *imported*, its objects are immediately
        registered to the importing server. This is a one-time operation and
        future changes to the imported server will not be reflected in the
        importing server. Server-level configurations and lifespans are not imported.
        When a server is imported with a prefix:
        - The tools are imported with prefixed names
          Example: If server has a tool named "get_weather", it will be
          available as "prefix_get_weather"
        - The resources are imported with prefixed URIs using the new format
          Example: If server has a resource with URI "weather://forecast", it will
          be available as "weather://prefix/forecast"
        - The templates are imported with prefixed URI templates using the new format
          Example: If server has a template with URI "weather://location/{id}", it will
          be available as "weather://prefix/location/{id}"
        - The prompts are imported with prefixed names
          Example: If server has a prompt named "weather_prompt", it will be available as
          "prefix_weather_prompt"
        When a server is imported without a prefix (prefix=None), its tools, resources,
        templates, and prompts are imported with their original names.
        Args:
            server: The FastMCP server to import
            prefix: Optional prefix to use for the imported server's objects. If None,
                objects are imported with their original names.
            tool_separator: Deprecated. Separator for tool names.
            resource_separator: Deprecated and ignored. Prefix is now
              applied using the protocol://prefix/path format
            prompt_separator: Deprecated. Separator for prompt names.
        """
⋮----
# Import tools from the server
⋮----
tool = tool.with_key(f"{prefix}_{key}")
⋮----
# Import resources and templates from the server
⋮----
resource_key = add_resource_prefix(
resource = resource.with_key(resource_key)
⋮----
template_key = add_resource_prefix(
template = template.with_key(template_key)
⋮----
# Import prompts from the server
⋮----
prompt = prompt.with_key(f"{prefix}_{key}")
⋮----
"""
        Create a FastMCP server from an OpenAPI specification.
        """
⋮----
"""
        Create a FastMCP server from a FastAPI application.
        """
⋮----
httpx_client_kwargs = {}
⋮----
client = httpx.AsyncClient(
name = name or app.title
⋮----
"""Create a FastMCP proxy server for the given backend.
        The `backend` argument can be either an existing `fastmcp.client.Client`
        instance or any value accepted as the `transport` argument of
        `fastmcp.client.Client`. This mirrors the convenience of the
        `fastmcp.client.Client` constructor.
        """
⋮----
client = backend
⋮----
client = Client(backend)
⋮----
"""
        Create a FastMCP proxy server from a FastMCP client.
        """
# Deprecated since 2.3.5
⋮----
"""
        Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not.
        Rules:
            - If the component's enabled property is False, always return False.
            - If both include_tags and exclude_tags are None, return True.
            - If exclude_tags is provided, check each exclude tag:
                - If the exclude tag is a string, it must be present in the input tags to exclude.
            - If include_tags is provided, check each include tag:
                - If the include tag is a string, it must be present in the input tags to include.
            - If include_tags is provided and none of the include tags match, return False.
            - If include_tags is not provided, return True.
        """
⋮----
@dataclass
class MountedServer
⋮----
prefix: str | None
server: FastMCP[Any]
resource_prefix_format: Literal["protocol", "path"] | None = None
⋮----
"""Add a prefix to a resource URI.
    Args:
        uri: The original resource URI
        prefix: The prefix to add
    Returns:
        The resource URI with the prefix added
    Examples:
        With new style:
        ```python
        add_resource_prefix("resource://path/to/resource", "prefix")
        "resource://prefix/path/to/resource"
        ```
        With legacy style:
        ```python
        add_resource_prefix("resource://path/to/resource", "prefix")
        "prefix+resource://path/to/resource"
        ```
        With absolute path:
        ```python
        add_resource_prefix("resource:///absolute/path", "prefix")
        "resource://prefix//absolute/path"
        ```
    Raises:
        ValueError: If the URI doesn't match the expected protocol://path format
    """
⋮----
# Get the server settings to check for legacy format preference
⋮----
prefix_format = fastmcp.settings.resource_prefix_format
⋮----
# Legacy style: prefix+protocol://path
⋮----
# New style: protocol://prefix/path
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
⋮----
# Add the prefix to the path
⋮----
"""Remove a prefix from a resource URI.
    Args:
        uri: The resource URI with a prefix
        prefix: The prefix to remove
        prefix_format: The format of the prefix to remove
    Returns:
        The resource URI with the prefix removed
    Examples:
        With new style:
        ```python
        remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
        "resource://path/to/resource"
        ```
        With legacy style:
        ```python
        remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
        "resource://path/to/resource"
        ```
        With absolute path:
        ```python
        remove_resource_prefix("resource://prefix//absolute/path", "prefix")
        "resource:///absolute/path"
        ```
    Raises:
        ValueError: If the URI doesn't match the expected protocol://path format
    """
⋮----
legacy_prefix = f"{prefix}+"
⋮----
# Check if the path starts with the prefix followed by a /
prefix_pattern = f"^{re.escape(prefix)}/(.*?)$"
path_match = re.match(prefix_pattern, path)
⋮----
# Return the URI without the prefix
⋮----
"""Check if a resource URI has a specific prefix.
    Args:
        uri: The resource URI to check
        prefix: The prefix to look for
    Returns:
        True if the URI has the specified prefix, False otherwise
    Examples:
        With new style:
        ```python
        has_resource_prefix("resource://prefix/path/to/resource", "prefix")
        True
        ```
        With legacy style:
        ```python
        has_resource_prefix("prefix+resource://path/to/resource", "prefix")
        True
        ```
        With other path:
        ```python
        has_resource_prefix("resource://other/path/to/resource", "prefix")
        False
        ```
    Raises:
        ValueError: If the URI doesn't match the expected protocol://path format
    """
⋮----
prefix_pattern = f"^{re.escape(prefix)}/"

================
File: src/fastmcp/tools/__init__.py
================
__all__ = ["Tool", "ToolManager", "FunctionTool", "forward", "forward_raw"]

================
File: src/fastmcp/tools/tool_manager.py
================
logger = get_logger(__name__)
class ToolManager
⋮----
"""Manages FastMCP tools."""
⋮----
# Default to "warn" if None is provided
⋮----
duplicate_behavior = "warn"
⋮----
def mount(self, server: MountedServer) -> None
⋮----
"""Adds a mounted server as a source for tools."""
⋮----
async def _load_tools(self, *, via_server: bool = False) -> dict[str, Tool]
⋮----
"""
        The single, consolidated recursive method for fetching tools. The 'via_server'
        parameter determines the communication path.
        - via_server=False: Manager-to-manager path for complete, unfiltered inventory
        - via_server=True: Server-to-server path for filtered MCP requests
        """
all_tools: dict[str, Tool] = {}
⋮----
# Use the server-to-server filtered path
child_results = await mounted.server._list_tools()
⋮----
# Use the manager-to-manager unfiltered path
child_results = await mounted.server._tool_manager.list_tools()
# The combination logic is the same for both paths
child_dict = {t.key: t for t in child_results}
⋮----
prefixed_tool = tool.with_key(f"{mounted.prefix}_{tool.key}")
⋮----
# Skip failed mounts silently, matches existing behavior
⋮----
# Finally, add local tools, which always take precedence
⋮----
async def has_tool(self, key: str) -> bool
⋮----
"""Check if a tool exists."""
tools = await self.get_tools()
⋮----
async def get_tool(self, key: str) -> Tool
⋮----
"""Get tool by key."""
⋮----
async def get_tools(self) -> dict[str, Tool]
⋮----
"""
        Gets the complete, unfiltered inventory of all tools.
        """
⋮----
async def list_tools(self) -> list[Tool]
⋮----
"""
        Lists all tools, applying protocol filtering.
        """
tools_dict = await self._load_tools(via_server=True)
⋮----
"""Add a tool to the server."""
# deprecated in 2.7.0
⋮----
tool = Tool.from_function(
⋮----
def add_tool(self, tool: Tool) -> Tool
⋮----
"""Register a tool with the server."""
existing = self._tools.get(tool.key)
⋮----
def remove_tool(self, key: str) -> None
⋮----
"""Remove a tool from the server.
        Args:
            key: The key of the tool to remove
        Raises:
            NotFoundError: If the tool is not found
        """
⋮----
async def call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult
⋮----
"""
        Internal API for servers: Finds and calls a tool, respecting the
        filtered protocol path.
        """
# 1. Check local tools first. The server will have already applied its filter.
⋮----
tool = await self.get_tool(key)
⋮----
# raise ToolErrors as-is
⋮----
# Handle other exceptions
⋮----
# Mask internal details
⋮----
# Include original error details
⋮----
# 2. Check mounted servers using the filtered protocol path.
⋮----
tool_key = key
⋮----
tool_key = key.removeprefix(f"{mounted.prefix}_")

================
File: src/fastmcp/tools/tool_transform.py
================
logger = get_logger(__name__)
# Context variable to store current transformed tool
_current_tool: ContextVar[TransformedTool | None] = ContextVar(
async def forward(**kwargs) -> ToolResult
⋮----
"""Forward to parent tool with argument transformation applied.
    This function can only be called from within a transformed tool's custom
    function. It applies argument transformation (renaming, validation) before
    calling the parent tool.
    For example, if the parent tool has args `x` and `y`, but the transformed
    tool has args `a` and `b`, and an `transform_args` was provided that maps `x` to
    `a` and `y` to `b`, then `forward(a=1, b=2)` will call the parent tool with
    `x=1` and `y=2`.
    Args:
        **kwargs: Arguments to forward to the parent tool (using transformed names).
    Returns:
        The ToolResult from the parent tool execution.
    Raises:
        RuntimeError: If called outside a transformed tool context.
        TypeError: If provided arguments don't match the transformed schema.
    """
tool = _current_tool.get()
⋮----
# Use the forwarding function that handles mapping
⋮----
async def forward_raw(**kwargs) -> ToolResult
⋮----
"""Forward directly to parent tool without transformation.
    This function bypasses all argument transformation and validation, calling the parent
    tool directly with the provided arguments. Use this when you need to call the parent
    with its original parameter names and structure.
    For example, if the parent tool has args `x` and `y`, then `forward_raw(x=1,
    y=2)` will call the parent tool with `x=1` and `y=2`.
    Args:
        **kwargs: Arguments to pass directly to the parent tool (using original names).
    Returns:
        The ToolResult from the parent tool execution.
    Raises:
        RuntimeError: If called outside a transformed tool context.
    """
⋮----
@dataclass(kw_only=True)
class ArgTransform
⋮----
"""Configuration for transforming a parent tool's argument.
    This class allows fine-grained control over how individual arguments are transformed
    when creating a new tool from an existing one. You can rename arguments, change their
    descriptions, add default values, or hide them from clients while passing constants.
    Attributes:
        name: New name for the argument. Use None to keep original name, or ... for no change.
        description: New description for the argument. Use None to remove description, or ... for no change.
        default: New default value for the argument. Use ... for no change.
        default_factory: Callable that returns a default value. Cannot be used with default.
        type: New type for the argument. Use ... for no change.
        hide: If True, hide this argument from clients but pass a constant value to parent.
        required: If True, make argument required (remove default). Use ... for no change.
        examples: Examples for the argument. Use ... for no change.
    Examples:
        Rename argument 'old_name' to 'new_name'
        ```python
        ArgTransform(name="new_name")
        ```
        Change description only
        ```python
        ArgTransform(description="Updated description")
        ```
        Add a default value (makes argument optional)
        ```python
        ArgTransform(default=42)
        ```
        Add a default factory (makes argument optional)
        ```python
        ArgTransform(default_factory=lambda: time.time())
        ```
        Change the type
        ```python
        ArgTransform(type=str)
        ```
        Hide the argument entirely from clients
        ```python
        ArgTransform(hide=True)
        ```
        Hide argument but pass a constant value to parent
        ```python
        ArgTransform(hide=True, default="constant_value")
        ```
        Hide argument but pass a factory-generated value to parent
        ```python
        ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
        ```
        Make an optional parameter required (removes any default)
        ```python
        ArgTransform(required=True)
        ```
        Combine multiple transformations
        ```python
        ArgTransform(name="new_name", description="New desc", default=None, type=int)
        ```
    """
name: str | NotSetT = NotSet
description: str | NotSetT = NotSet
default: Any | NotSetT = NotSet
default_factory: Callable[[], Any] | NotSetT = NotSet
type: Any | NotSetT = NotSet
hide: bool = False
required: Literal[True] | NotSetT = NotSet
examples: Any | NotSetT = NotSet
def __post_init__(self)
⋮----
"""Validate that only one of default or default_factory is provided."""
has_default = self.default is not NotSet
has_factory = self.default_factory is not NotSet
⋮----
class TransformedTool(Tool)
⋮----
"""A tool that is transformed from another tool.
    This class represents a tool that has been created by transforming another tool.
    It supports argument renaming, schema modification, custom function injection,
    structured output control, and provides context for the forward() and forward_raw() functions.
    The transformation can be purely schema-based (argument renaming, dropping, etc.)
    or can include a custom function that uses forward() to call the parent tool
    with transformed arguments. Output schemas and structured outputs are automatically
    inherited from the parent tool but can be overridden or disabled.
    Attributes:
        parent_tool: The original tool that this tool was transformed from.
        fn: The function to execute when this tool is called (either the forwarding
            function for pure transformations or a custom user function).
        forwarding_fn: Internal function that handles argument transformation and
            validation when forward() is called from custom functions.
    """
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
parent_tool: Tool
fn: Callable[..., Any]
forwarding_fn: Callable[..., Any]  # Always present, handles arg transformation
transform_args: dict[str, ArgTransform]
async def run(self, arguments: dict[str, Any]) -> ToolResult
⋮----
"""Run the tool with context set for forward() functions.
        This method executes the tool's function while setting up the context
        that allows forward() and forward_raw() to work correctly within custom
        functions.
        Args:
            arguments: Dictionary of arguments to pass to the tool's function.
        Returns:
            ToolResult object containing content and optional structured output.
        """
⋮----
# Fill in missing arguments with schema defaults to ensure
# ArgTransform defaults take precedence over function defaults
arguments = arguments.copy()
properties = self.parameters.get("properties", {})
⋮----
# Check if this parameter has a default_factory from transform_args
# We need to call the factory for each run, not use the cached schema value
has_factory_default = False
⋮----
# Find the original parameter name that maps to this param_name
⋮----
transform_name = (
⋮----
# Type check to ensure default_factory is callable
⋮----
has_factory_default = True
⋮----
token = _current_tool.set(self)
⋮----
result = await self.fn(**arguments)
# If transform function returns ToolResult, respect our output_schema setting
⋮----
# Check if this is from a custom function that returns ToolResult
⋮----
return_annotation = inspect.signature(self.fn).return_annotation
⋮----
# Custom function returns ToolResult - preserve its content
⋮----
# Forwarded call with disabled schema - strip structured content
⋮----
# Non-object explicit schemas disable structured content
⋮----
# Otherwise convert to content and create ToolResult with proper structured content
⋮----
unstructured_result = _convert_to_content(
# Handle structured content based on output schema
⋮----
# Schema says wrap - always wrap in result key
structured_output = {"result": result}
⋮----
# Object schemas - use result directly
# User is responsible for returning dict-compatible data
structured_output = result
⋮----
structured_output = None
⋮----
"""Create a transformed tool from a parent tool.
        Args:
            tool: The parent tool to transform.
            transform_fn: Optional custom function. Can use forward() and forward_raw()
                to call the parent tool. Functions with **kwargs receive transformed
                argument names.
            name: New name for the tool. Defaults to parent tool's name.
            transform_args: Optional transformations for parent tool arguments.
                Only specified arguments are transformed, others pass through unchanged:
                - Simple rename (str)
                - Complex transformation (rename/description/default/drop) (ArgTransform)
                - Drop the argument (None)
            description: New description. Defaults to parent's description.
            tags: New tags. Defaults to parent's tags.
            annotations: New annotations. Defaults to parent's annotations.
            output_schema: Control output schema for structured outputs:
                - None (default): Inherit from transform_fn if available, then parent tool
                - dict: Use custom output schema
                - False: Disable output schema and structured outputs
            serializer: New serializer. Defaults to parent's serializer.
        Returns:
            TransformedTool with the specified transformations.
        Examples:
            # Transform specific arguments only
            ```python
            Tool.from_tool(parent, transform_args={"old": "new"})  # Others unchanged
            ```
            # Custom function with partial transforms
            ```python
            async def custom(x: int, y: int) -> str:
                result = await forward(x=x, y=y)
                return f"Custom: {result}"
            Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
            ```
            # Using **kwargs (gets all args, transformed and untransformed)
            ```python
            async def flexible(**kwargs) -> str:
                result = await forward(**kwargs)
                return f"Got: {kwargs}"
            Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
            ```
            # Control structured outputs and schemas
            ```python
            # Custom output schema
            Tool.from_tool(parent, output_schema={
                "type": "object",
                "properties": {"status": {"type": "string"}}
            })
            # Disable structured outputs
            Tool.from_tool(parent, output_schema=False)
            # Return ToolResult for full control
            async def custom_output(**kwargs) -> ToolResult:
                result = await forward(**kwargs)
                return ToolResult(
                    content=[TextContent(text="Summary")],
                    structured_content={"processed": True}
                )
            ```
        """
transform_args = transform_args or {}
# Validate transform_args
parent_params = set(tool.parameters.get("properties", {}).keys())
unknown_args = set(transform_args.keys()) - parent_params
⋮----
# Always create the forwarding transform
⋮----
# Handle output schema with smart fallback
⋮----
final_output_schema = None
⋮----
# Explicit schema provided - use as-is
final_output_schema = output_schema
⋮----
# Smart fallback: try custom function, then parent, then None
⋮----
parsed_fn = ParsedFunction.from_function(transform_fn, validate=False)
final_output_schema = _wrap_schema_if_needed(parsed_fn.output_schema)
⋮----
# Check if function returns ToolResult - if so, don't fall back to parent
⋮----
return_annotation = inspect.signature(
⋮----
final_output_schema = tool.output_schema
⋮----
# User wants pure transformation - use forwarding_fn as the main function
final_fn = forwarding_fn
final_schema = schema
⋮----
# User provided custom function - merge schemas
⋮----
final_fn = transform_fn
has_kwargs = cls._function_has_kwargs(transform_fn)
# Validate function parameters against transformed schema
fn_params = set(parsed_fn.input_schema.get("properties", {}).keys())
transformed_params = set(schema.get("properties", {}).keys())
⋮----
# Without **kwargs, function must declare all transformed params
# Check if function is missing any parameters required after transformation
missing_params = transformed_params - fn_params
⋮----
# ArgTransform takes precedence over function signature
# Start with function schema as base, then override with transformed schema
final_schema = cls._merge_schema_with_precedence(
⋮----
# With **kwargs, function can access all transformed params
⋮----
# No validation needed - kwargs makes everything accessible
⋮----
# Additional validation: check for naming conflicts after transformation
⋮----
new_names = []
⋮----
# Check for duplicate names after transformation
name_counts = {}
⋮----
duplicates = [
⋮----
final_description = description if description is not None else tool.description
transformed_tool = cls(
⋮----
"""Create schema and forwarding function that encapsulates all transformation logic.
        This method builds a new JSON schema for the transformed tool and creates a
        forwarding function that validates arguments against the new schema and maps
        them back to the parent tool's expected arguments.
        Args:
            parent_tool: The original tool to transform.
            transform_args: Dictionary defining how to transform each argument.
        Returns:
            A tuple containing:
            - The new JSON schema for the transformed tool as a dictionary
            - Async function that validates and forwards calls to the parent tool
        """
# Build transformed schema and mapping
parent_props = parent_tool.parameters.get("properties", {}).copy()
parent_required = set(parent_tool.parameters.get("required", []))
new_props = {}
new_required = set()
new_to_old = {}
hidden_defaults = {}  # Track hidden parameters with constant values
⋮----
# Check if parameter is in transform_args
⋮----
transform = transform_args[old_name]
⋮----
# Default behavior - pass through (no transformation)
transform = ArgTransform()  # Default ArgTransform with no changes
# Handle hidden parameters with defaults
⋮----
# Validate that hidden parameters without user defaults have parent defaults
has_user_default = (
⋮----
# Store info for later factory calling or direct value
⋮----
# Skip adding to schema (not exposed to clients)
⋮----
transform_result = cls._apply_single_transform(
⋮----
schema = {
# Create forwarding function that closes over everything it needs
async def _forward(**kwargs)
⋮----
# Validate arguments
valid_args = set(new_props.keys())
provided_args = set(kwargs.keys())
unknown_args = provided_args - valid_args
⋮----
# Check required arguments
missing_args = new_required - provided_args
⋮----
# Map arguments to parent names
parent_args = {}
⋮----
old_name = new_to_old.get(new_name, new_name)
⋮----
# Add hidden defaults (constant values for hidden parameters)
⋮----
# Type check to ensure default_factory is callable
⋮----
"""Apply transformation to a single parameter.
        This method handles the transformation of a single argument according to
        the specified transformation rules.
        Args:
            old_name: Original name of the parameter.
            old_schema: Original JSON schema for the parameter.
            transform: ArgTransform object specifying how to transform the parameter.
            is_required: Whether the original parameter was required.
        Returns:
            Tuple of (new_name, new_schema, new_is_required) if parameter should be kept,
            None if parameter should be dropped.
        """
⋮----
# Handle name transformation - ensure we always have a string
⋮----
new_name = transform.name if transform.name is not None else old_name
⋮----
new_name = old_name
# Ensure new_name is always a string
⋮----
new_schema = old_schema.copy()
# Handle description transformation
⋮----
new_schema.pop("description", None)  # Remove description
⋮----
# Handle required transformation first
⋮----
is_required = bool(transform.required)
⋮----
# Remove any existing default when making required
⋮----
# Handle default value transformation (only if not making required)
⋮----
is_required = False
# Handle type transformation
⋮----
# Use TypeAdapter to get proper JSON schema for the type
type_schema = get_cached_typeadapter(transform.type).json_schema()
# Update the schema with the type information from TypeAdapter
⋮----
# Handle examples transformation
⋮----
"""Merge two schemas, with the override schema taking precedence.
        Args:
            base_schema: Base schema to start with
            override_schema: Schema that takes precedence for overlapping properties
        Returns:
            Merged schema with override taking precedence
        """
merged_props = base_schema.get("properties", {}).copy()
merged_required = set(base_schema.get("required", []))
override_props = override_schema.get("properties", {})
override_required = set(override_schema.get("required", []))
# Override properties
⋮----
# Merge the schemas, with override taking precedence
base_param = merged_props[param_name].copy()
⋮----
# Handle required parameters - override takes complete precedence
# Start with override's required set
final_required = override_required.copy()
# For parameters not in override, inherit base requirement status
# but only if they don't have a default in the final merged properties
⋮----
# Parameter not mentioned in override, keep base requirement status
⋮----
# Parameter in override but no default, keep required if it was required in base
⋮----
# Override doesn't specify it as required, and it has no default,
# so inherit from base
⋮----
# Remove any parameters that have defaults (they become optional)
⋮----
@staticmethod
    def _function_has_kwargs(fn: Callable[..., Any]) -> bool
⋮----
"""Check if function accepts **kwargs.
        This determines whether a custom function can accept arbitrary keyword arguments,
        which affects how schemas are merged during tool transformation.
        Args:
            fn: Function to inspect.
        Returns:
            True if the function has a **kwargs parameter, False otherwise.
        """
sig = inspect.signature(fn)

================
File: src/fastmcp/tools/tool.py
================
logger = get_logger(__name__)
class _UnserializableType
def default_serializer(data: Any) -> str
def _wrap_schema_if_needed(schema: dict[str, Any] | None) -> dict[str, Any] | None
⋮----
"""Wrap non-object schemas with result property for structured output.
    This wrapping allows primitive types (int, str, etc.) to be returned as
    structured content by placing them under a "result" key.
    Args:
        schema: The JSON schema to potentially wrap
    Returns:
        Wrapped schema if needed, or original schema if already an object type
    """
⋮----
class ToolResult
⋮----
content = structured_content
⋮----
structured_content = pydantic_core.to_jsonable_python(
⋮----
class Tool(FastMCPComponent)
⋮----
"""Internal tool registration info."""
parameters: Annotated[
output_schema: Annotated[
annotations: Annotated[
serializer: Annotated[
def enable(self) -> None
⋮----
context = get_context()
context._queue_tool_list_changed()  # type: ignore[private-use]
⋮----
pass  # No context available
def disable(self) -> None
def to_mcp_tool(self, **overrides: Any) -> MCPTool
⋮----
title = self.title
⋮----
title = self.annotations.title
⋮----
title = None
kwargs = {
⋮----
"""Create a Tool from a function."""
⋮----
async def run(self, arguments: dict[str, Any]) -> ToolResult
⋮----
"""
        Run the tool with arguments.
        This method is not implemented in the base Tool class and must be
        implemented by subclasses.
        `run()` can EITHER return a list of ContentBlocks, or a tuple of
        (list of ContentBlocks, dict of structured output).
        """
⋮----
class FunctionTool(Tool)
⋮----
fn: Callable[..., Any]
⋮----
parsed_fn = ParsedFunction.from_function(fn, exclude_args=exclude_args)
⋮----
output_schema = _wrap_schema_if_needed(parsed_fn.output_schema)
⋮----
output_schema = None
# Note: explicit schemas (dict) are used as-is without auto-wrapping
# Validate that explicit schemas are object type for structured content
⋮----
"""Run the tool with arguments."""
⋮----
arguments = arguments.copy()
context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
⋮----
type_adapter = get_cached_typeadapter(self.fn)
result = type_adapter.validate_python(arguments)
⋮----
result = await result
⋮----
unstructured_result = _convert_to_content(result, serializer=self.serializer)
structured_output = None
# First handle structured content based on output schema, if any
⋮----
# Schema says wrap - always wrap in result key
structured_output = {"result": result}
⋮----
structured_output = result
# If no output schema, try to serialize the result. If it is a dict, use
# it as structured content. If it is not a dict, ignore it.
⋮----
structured_output = pydantic_core.to_jsonable_python(result)
⋮----
@dataclass
class ParsedFunction
⋮----
name: str
description: str | None
input_schema: dict[str, Any]
output_schema: dict[str, Any] | None
⋮----
sig = inspect.signature(fn)
# Reject functions with *args or **kwargs
⋮----
# Reject exclude_args that don't exist in the function or don't have a default value
⋮----
param = sig.parameters[arg_name]
⋮----
# collect name and doc before we potentially modify the function
fn_name = getattr(fn, "__name__", None) or fn.__class__.__name__
fn_doc = inspect.getdoc(fn)
# if the fn is a callable class, we need to get the __call__ method from here out
⋮----
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
⋮----
fn = fn.__func__
prune_params: list[str] = []
context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
⋮----
input_type_adapter = get_cached_typeadapter(fn)
input_schema = input_type_adapter.json_schema()
input_schema = compress_schema(input_schema, prune_params=prune_params)
⋮----
output_type = inspect.signature(fn).return_annotation
⋮----
# there are a variety of types that we don't want to attempt to
# serialize because they are either used by FastMCP internally,
# or are MCP content types that explicitly don't form structured
# content. By replacing them with an explicitly unserializable type,
# we ensure that no output schema is automatically generated.
output_type = replace_type(
⋮----
output_type_adapter = get_cached_typeadapter(output_type)
output_schema = output_type_adapter.json_schema()
⋮----
"""Convert a result to a sequence of content objects."""
⋮----
# if the result is a list, then it could either be a list of MCP types,
# or a "regular" list that the tool is returning, or a mix of both.
#
# so we extract all the MCP types / images and convert them as individual content elements,
# and aggregate the rest as a single content element
mcp_types = []
other_content = []
⋮----
other_content = _convert_to_content(
⋮----
result = default_serializer(result)
⋮----
result = serializer(result)

================
File: src/fastmcp/utilities/__init__.py
================
"""FastMCP utility modules."""

================
File: src/fastmcp/utilities/cache.py
================
UTC = datetime.timezone.utc
class TimedCache
⋮----
NOT_FOUND = object()
def __init__(self, expiration: datetime.timedelta)
def set(self, key: Any, value: Any) -> None
⋮----
expires = datetime.datetime.now(UTC) + self.expiration
⋮----
def get(self, key: Any) -> Any
⋮----
value = self.cache.get(key)
⋮----
def clear(self) -> None

================
File: src/fastmcp/utilities/components.py
================
T = TypeVar("T")
def _convert_set_default_none(maybe_set: set[T] | Sequence[T] | None) -> set[T]
⋮----
"""Convert a sequence to a set, defaulting to an empty set if None."""
⋮----
class FastMCPComponent(FastMCPBaseModel)
⋮----
"""Base class for FastMCP tools, prompts, resources, and resource templates."""
name: str = Field(
title: str | None = Field(
description: str | None = Field(
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
enabled: bool = Field(
_key: str | None = PrivateAttr()
def __init__(self, *, key: str | None = None, **kwargs: Any) -> None
⋮----
@property
    def key(self) -> str
⋮----
"""
        The key of the component. This is used for internal bookkeeping
        and may reflect e.g. prefixes or other identifiers. You should not depend on
        keys having a certain value, as the same tool loaded from different
        hierarchies of servers may have different keys.
        """
⋮----
def with_key(self, key: str) -> Self
def __eq__(self, other: object) -> bool
def __repr__(self) -> str
def enable(self) -> None
⋮----
"""Enable the component."""
⋮----
def disable(self) -> None
⋮----
"""Disable the component."""
⋮----
def get_display_name(self) -> str
⋮----
"""Get the display name for this component, preferring title over name."""

================
File: src/fastmcp/utilities/exceptions.py
================
def iter_exc(group: BaseExceptionGroup)
def _exception_handler(group: BaseExceptionGroup)
# this catch handler is used to catch taskgroup exception groups and raise the
# first exception. This allows more sane debugging.
_catch_handlers: Mapping[

================
File: src/fastmcp/utilities/http.py
================
def find_available_port() -> int
⋮----
"""Find an available port by letting the OS assign one."""

================
File: src/fastmcp/utilities/inspect.py
================
"""Utilities for inspecting FastMCP instances."""
⋮----
@dataclass
class ToolInfo
⋮----
"""Information about a tool."""
key: str
name: str
description: str | None
input_schema: dict[str, Any]
annotations: dict[str, Any] | None = None
tags: list[str] | None = None
enabled: bool | None = None
⋮----
@dataclass
class PromptInfo
⋮----
"""Information about a prompt."""
⋮----
arguments: list[dict[str, Any]] | None = None
⋮----
@dataclass
class ResourceInfo
⋮----
"""Information about a resource."""
⋮----
uri: str
name: str | None
⋮----
mime_type: str | None = None
⋮----
@dataclass
class TemplateInfo
⋮----
"""Information about a resource template."""
⋮----
uri_template: str
⋮----
@dataclass
class FastMCPInfo
⋮----
"""Information extracted from a FastMCP instance."""
⋮----
instructions: str | None
fastmcp_version: str
mcp_version: str
server_version: str
tools: list[ToolInfo]
prompts: list[PromptInfo]
resources: list[ResourceInfo]
templates: list[TemplateInfo]
capabilities: dict[str, Any]
async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo
⋮----
"""Extract information from a FastMCP v2.x instance.
    Args:
        mcp: The FastMCP v2.x instance to inspect
    Returns:
        FastMCPInfo dataclass containing the extracted information
    """
# Get all the components using FastMCP2's direct methods
tools_dict = await mcp.get_tools()
prompts_dict = await mcp.get_prompts()
resources_dict = await mcp.get_resources()
templates_dict = await mcp.get_resource_templates()
# Extract detailed tool information
tool_infos = []
⋮----
# Convert to MCP tool to get input schema
mcp_tool = tool.to_mcp_tool(name=key)
⋮----
# Extract detailed prompt information
prompt_infos = []
⋮----
# Extract detailed resource information
resource_infos = []
⋮----
uri=key,  # For v2, key is the URI
⋮----
# Extract detailed template information
template_infos = []
⋮----
uri_template=key,  # For v2, key is the URI template
⋮----
# Basic MCP capabilities that FastMCP supports
capabilities = {
⋮----
server_version=fastmcp.__version__,  # v2.x uses FastMCP version
⋮----
async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo
⋮----
"""Extract information from a FastMCP v1.x instance using a Client.
    Args:
        mcp: The FastMCP v1.x instance to inspect
    Returns:
        FastMCPInfo dataclass containing the extracted information
    """
⋮----
# Use a client to interact with the FastMCP1x server
⋮----
# Get components via client calls (these return MCP objects)
mcp_tools = await client.list_tools()
mcp_prompts = await client.list_prompts()
mcp_resources = await client.list_resources()
# Try to get resource templates (FastMCP 1.x does have templates)
⋮----
mcp_templates = await client.list_resource_templates()
⋮----
mcp_templates = []
# Extract detailed tool information from MCP Tool objects
⋮----
# Extract annotations if they exist
annotations = None
⋮----
annotations = mcp_tool.annotations.model_dump()
⋮----
annotations = mcp_tool.annotations
⋮----
key=mcp_tool.name,  # For 1.x, key and name are the same
⋮----
tags=None,  # 1.x doesn't have tags
enabled=None,  # 1.x doesn't have enabled field
⋮----
# Extract detailed prompt information from MCP Prompt objects
⋮----
# Convert arguments if they exist
arguments = None
⋮----
arguments = [arg.model_dump() for arg in mcp_prompt.arguments]
⋮----
key=mcp_prompt.name,  # For 1.x, key and name are the same
⋮----
# Extract detailed resource information from MCP Resource objects
⋮----
key=str(mcp_resource.uri),  # For 1.x, key and uri are the same
⋮----
# Extract detailed template information from MCP ResourceTemplate objects
⋮----
),  # For 1.x, key and uriTemplate are the same
⋮----
# Basic MCP capabilities
⋮----
fastmcp_version=fastmcp.__version__,  # Report current fastmcp version
⋮----
server_version="1.0",  # FastMCP 1.x version
⋮----
templates=template_infos,  # FastMCP1x does have templates
⋮----
def _is_fastmcp_v1(mcp: Any) -> bool
⋮----
"""Check if the given instance is a FastMCP v1.x instance."""
# Check if it's an instance of FastMCP1x and not FastMCP2
⋮----
async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo
⋮----
"""Extract information from a FastMCP instance into a dataclass.
    This function automatically detects whether the instance is FastMCP v1.x or v2.x
    and uses the appropriate extraction method.
    Args:
        mcp: The FastMCP instance to inspect (v1.x or v2.x)
    Returns:
        FastMCPInfo dataclass containing the extracted information
    """

================
File: src/fastmcp/utilities/json_schema_type.py
================
"""Convert JSON Schema to Python types with validation.
The json_schema_to_type function converts a JSON Schema into a Python type that can be used
for validation with Pydantic. It supports:
- Basic types (string, number, integer, boolean, null)
- Complex types (arrays, objects)
- Format constraints (date-time, email, uri)
- Numeric constraints (minimum, maximum, multipleOf)
- String constraints (minLength, maxLength, pattern)
- Array constraints (minItems, maxItems, uniqueItems)
- Object properties with defaults
- References and recursive schemas
- Enums and constants
- Union types
Example:
    ```python
    schema = {
        "type": "object",
        "properties": {
            "name": {"type": "string", "minLength": 1},
            "age": {"type": "integer", "minimum": 0},
            "email": {"type": "string", "format": "email"}
        },
        "required": ["name", "age"]
    }
    # Name is optional and will be inferred from schema's "title" property if not provided
    Person = json_schema_to_type(schema)
    # Creates a validated dataclass with name, age, and optional email fields
    ```
"""
⋮----
__all__ = ["json_schema_to_type", "JSONSchema"]
FORMAT_TYPES: dict[str, Any] = {
_classes: dict[tuple[str, Any], type | None] = {}
class JSONSchema(TypedDict)
⋮----
type: NotRequired[str | list[str]]
properties: NotRequired[dict[str, JSONSchema]]
required: NotRequired[list[str]]
additionalProperties: NotRequired[bool | JSONSchema]
items: NotRequired[JSONSchema | list[JSONSchema]]
enum: NotRequired[list[Any]]
const: NotRequired[Any]
default: NotRequired[Any]
description: NotRequired[str]
title: NotRequired[str]
examples: NotRequired[list[Any]]
format: NotRequired[str]
allOf: NotRequired[list[JSONSchema]]
anyOf: NotRequired[list[JSONSchema]]
oneOf: NotRequired[list[JSONSchema]]
not_: NotRequired[JSONSchema]
definitions: NotRequired[dict[str, JSONSchema]]
dependencies: NotRequired[dict[str, JSONSchema | list[str]]]
pattern: NotRequired[str]
minLength: NotRequired[int]
maxLength: NotRequired[int]
minimum: NotRequired[int | float]
maximum: NotRequired[int | float]
exclusiveMinimum: NotRequired[int | float]
exclusiveMaximum: NotRequired[int | float]
multipleOf: NotRequired[int | float]
uniqueItems: NotRequired[bool]
minItems: NotRequired[int]
maxItems: NotRequired[int]
additionalItems: NotRequired[bool | JSONSchema]
⋮----
"""Convert JSON schema to appropriate Python type with validation.
    Args:
        schema: A JSON Schema dictionary defining the type structure and validation rules
        name: Optional name for object schemas. Only allowed when schema type is "object".
            If not provided for objects, name will be inferred from schema's "title"
            property or default to "Root".
    Returns:
        A Python type (typically a dataclass for objects) with Pydantic validation
    Raises:
        ValueError: If a name is provided for a non-object schema
    Examples:
        Create a dataclass from an object schema:
        ```python
        schema = {
            "type": "object",
            "title": "Person",
            "properties": {
                "name": {"type": "string", "minLength": 1},
                "age": {"type": "integer", "minimum": 0},
                "email": {"type": "string", "format": "email"}
            },
            "required": ["name", "age"]
        }
        Person = json_schema_to_type(schema)
        # Creates a dataclass with name, age, and optional email fields:
        # @dataclass
        # class Person:
        #     name: str
        #     age: int
        #     email: str | None = None
        ```
        Person(name="John", age=30)
        Create a scalar type with constraints:
        ```python
        schema = {
            "type": "string",
            "minLength": 3,
            "pattern": "^[A-Z][a-z]+$"
        }
        NameType = json_schema_to_type(schema)
        # Creates Annotated[str, StringConstraints(min_length=3, pattern="^[A-Z][a-z]+$")]
        @dataclass
        class Name:
            name: NameType
        ```
    """
# Always use the top-level schema for references
⋮----
# If no properties defined but has additionalProperties, return typed dict
⋮----
additional_props = schema["additionalProperties"]
⋮----
return dict[str, Any]  # type: ignore - additionalProperties: true means dict[str, Any]
⋮----
# Handle typed dictionaries like dict[str, str]
value_type = _schema_to_type(additional_props, schemas=schema)
return dict[str, value_type]  # type: ignore
# If no properties and no additionalProperties, default to dict[str, Any] for safety
⋮----
return dict[str, Any]  # type: ignore
# If has properties AND additionalProperties is True, use Pydantic BaseModel
⋮----
# Otherwise use fast dataclass
⋮----
result = _schema_to_type(schema, schemas=schema)
return result  # type: ignore[return-value]
def _hash_schema(schema: Mapping[str, Any]) -> str
⋮----
"""Generate a deterministic hash for schema caching."""
⋮----
def _resolve_ref(ref: str, schemas: Mapping[str, Any]) -> Mapping[str, Any]
⋮----
"""Resolve JSON Schema reference to target schema."""
path = ref.replace("#/", "").split("/")
current = schemas
⋮----
current = current.get(part, {})
⋮----
def _create_string_type(schema: Mapping[str, Any]) -> type | Annotated[Any, ...]
⋮----
"""Create string type with optional constraints."""
⋮----
return Literal[schema["const"]]  # type: ignore
⋮----
constraints = {
⋮----
"""Create numeric type with optional constraints."""
⋮----
def _create_enum(name: str, values: list[Any]) -> type
⋮----
"""Create enum type from list of values."""
# Always return Literal for enum fields to preserve the literal nature
return Literal[tuple(values)]  # type: ignore[return-value]
⋮----
"""Create list/set type with optional constraints."""
items = schema.get("items", {})
⋮----
# Handle positional item schemas
item_types = [_schema_to_type(s, schemas) for s in items]
combined = Union[tuple(item_types)]  # type: ignore # noqa: UP007
base = list[combined]
⋮----
# Handle single item schema
item_type = _schema_to_type(items, schemas)
base_class = set if schema.get("uniqueItems") else list
base = base_class[item_type]  # type: ignore[misc]
⋮----
def _return_Any() -> Any
⋮----
"""Get the appropriate type handler for the schema."""
type_handlers: dict[str, Callable[..., Any]] = {  # TODO
⋮----
"string": lambda s: _create_string_type(s),  # type: ignore
"integer": lambda s: _create_numeric_type(int, s),  # type: ignore
"number": lambda s: _create_numeric_type(float, s),  # type: ignore
"boolean": lambda _: bool,  # type: ignore
"null": lambda _: type(None),  # type: ignore
"array": lambda s: _create_array_type(s, schemas),  # type: ignore
⋮----
),  # type: ignore
⋮----
"""Convert schema to appropriate Python type."""
⋮----
# Handle references first
⋮----
ref = schema["$ref"]
# Handle self-reference
⋮----
return ForwardRef(schema.get("title", "Root"))  # type: ignore[return-value]
⋮----
# Handle anyOf unions
⋮----
types: list[type | Any] = []
⋮----
# Special handling for dict-like objects in unions
⋮----
# This is a dict type, handle it directly
additional_props = subschema["additionalProperties"]
⋮----
types.append(dict[str, Any])  # type: ignore
⋮----
value_type = _schema_to_type(additional_props, schemas)
types.append(dict[str, value_type])  # type: ignore
⋮----
# Check if one of the types is None (null)
has_null = type(None) in types
types = [t for t in types if t is not type(None)]
⋮----
return types[0] | None  # type: ignore
⋮----
return Union[tuple(types + [type(None)])]  # type: ignore # noqa: UP007
⋮----
return Union[tuple(types)]  # type: ignore # noqa: UP007
schema_type = schema.get("type")
⋮----
return Any  # type: ignore[return-value]
⋮----
# Create a copy of the schema for each type, but keep all constraints
⋮----
type_schema = dict(schema)
⋮----
return Union[tuple(types)]  # type: ignore # noqa: UP007
⋮----
def _sanitize_name(name: str) -> str
⋮----
"""Convert string to valid Python identifier."""
original_name = name
# Step 1: replace everything except [0-9a-zA-Z_] with underscores
cleaned = re.sub(r"[^0-9a-zA-Z_]", "_", name)
# Step 2: deduplicate underscores
cleaned = re.sub(r"__+", "_", cleaned)
# Step 3: if the first char of original name isn't a letter or underscore, prepend field_
⋮----
cleaned = f"field_{cleaned}"
# Step 4: deduplicate again
⋮----
# Step 5: only strip trailing underscores if they weren't in the original name
⋮----
cleaned = cleaned.rstrip("_")
⋮----
"""Get default value with proper priority ordering.
    1. Value from parent's default if it exists
    2. Property's own default if it exists
    3. None
    """
⋮----
"""Create a field with simplified default handling."""
# Always use None as default for complex types
⋮----
# For simple types, use the value directly
⋮----
"""Create Pydantic BaseModel from object schema with additionalProperties."""
name = name or schema.get("title", "Root")
assert name is not None  # Should not be None after the or operation
sanitized_name = _sanitize_name(name)
schema_hash = _hash_schema(schema)
cache_key = (schema_hash, sanitized_name)
# Return existing class if already built
⋮----
existing = _classes[cache_key]
⋮----
return ForwardRef(sanitized_name)  # type: ignore[return-value]
⋮----
# Place placeholder for recursive references
⋮----
properties = schema.get("properties", {})
required = schema.get("required", [])
# Build field annotations and defaults
annotations = {}
defaults = {}
⋮----
field_type = _schema_to_type(prop_schema, schemas or {})
# Handle defaults
default_value = prop_schema.get("default", MISSING)
⋮----
annotations[prop_name] = Union[field_type, type(None)]  # type: ignore[misc]  # noqa: UP007
⋮----
# Create Pydantic model class
cls_dict = {
cls = type(sanitized_name, (BaseModel,), cls_dict)
# Store completed class
⋮----
"""Create dataclass from object schema."""
⋮----
# Sanitize name for class creation
⋮----
original_schema = dict(schema)  # Store copy for validator
⋮----
schema = _resolve_ref(ref, schemas or {})
⋮----
fields: list[tuple[Any, ...]] = []
⋮----
field_name = _sanitize_name(prop_name)
# Check for self-reference in property
⋮----
field_type = ForwardRef(sanitized_name)
⋮----
default_val = prop_schema.get("default", MISSING)
is_required = prop_name in required
# Include alias in field metadata
meta = {"alias": prop_name}
⋮----
field_def = field(
⋮----
field_def = field(default=default_val, metadata=meta)
⋮----
field_def = field(metadata=meta)
⋮----
field_def = field(default=None, metadata=meta)
⋮----
fields.append((field_name, Union[field_type, type(None)], field_def))  # type: ignore[misc]  # noqa: UP007
cls = make_dataclass(sanitized_name, fields, kw_only=True)
# Add model validator for defaults
⋮----
@model_validator(mode="before")
@classmethod
    def _apply_defaults(cls, data: Mapping[str, Any])
⋮----
"""Merge defaults with provided data at all levels."""
# If we have no data
⋮----
# Start with parent default if available
⋮----
result = dict(parent_default)
# Otherwise use schema default if available
⋮----
result = dict(schema["default"])
# Otherwise start empty
⋮----
result = {}
# If we have data and a parent default, merge them
⋮----
# recursively merge nested dicts
⋮----
# Otherwise just use the data
⋮----
result = dict(data)
# For each property in the schema
⋮----
# If property is missing, apply defaults in priority order
⋮----
# If property exists and is an object, recursively merge
⋮----
# Get the appropriate default for this nested object
nested_default = None
⋮----
nested_default = parent_default[prop_name]
⋮----
nested_default = prop_schema["default"]

================
File: src/fastmcp/utilities/json_schema.py
================
def _prune_param(schema: dict, param: str) -> dict
⋮----
"""Return a new schema with *param* removed from `properties`, `required`,
    and (if no longer referenced) `$defs`.
    """
# ── 1. drop from properties/required ──────────────────────────────
props = schema.get("properties", {})
removed = props.pop(param, None)
if removed is None:  # nothing to do
⋮----
# Keep empty properties object rather than removing it entirely
⋮----
def _prune_unused_defs(schema: dict) -> dict
⋮----
"""Walk the schema and prune unused defs."""
root_defs: set[str] = set()
referenced_by: defaultdict[str, list] = defaultdict(list)
defs = schema.get("$defs")
⋮----
# Process $ref for definition tracking
ref = node.get("$ref")
⋮----
def_name = ref.split("/")[-1]
⋮----
# Walk children
⋮----
# Traverse the schema once, skipping the $defs
⋮----
# Now figure out what defs reference other defs
⋮----
# Figure out what defs were referenced directly or recursively
def def_is_referenced(def_name)
⋮----
references = referenced_by.get(def_name)
⋮----
# Remove orphaned definitions if requested
⋮----
"""Walk the schema and optionally prune titles and additionalProperties: false."""
def walk(node: object) -> None
⋮----
# Remove title if requested
⋮----
# Remove additionalProperties: false at any level if requested
⋮----
def _prune_additional_properties(schema: dict) -> dict
⋮----
"""Remove additionalProperties from the schema if it is False."""
⋮----
"""
    Remove the given parameters from the schema.
    Args:
        schema: The schema to compress
        prune_params: List of parameter names to remove from properties
        prune_defs: Whether to remove unused definitions
        prune_additional_properties: Whether to remove additionalProperties: false
        prune_titles: Whether to remove title fields from the schema
    """
# Make a copy so we don't modify the original
schema = copy.deepcopy(schema)
# Remove specific parameters if requested
⋮----
schema = _prune_param(schema, param=param)
# Do a single walk to handle pruning operations
⋮----
schema = _walk_and_prune(
⋮----
schema = _prune_unused_defs(schema)

================
File: src/fastmcp/utilities/logging.py
================
"""Logging utilities for FastMCP."""
⋮----
def get_logger(name: str) -> logging.Logger
⋮----
"""Get a logger nested under FastMCP namespace.
    Args:
        name: the name of the logger, which will be prefixed with 'FastMCP.'
    Returns:
        a configured logger instance
    """
⋮----
"""
    Configure logging for FastMCP.
    Args:
        logger: the logger to configure
        level: the log level to use
    """
⋮----
logger = logging.getLogger("FastMCP")
# Only configure the FastMCP logger namespace
handler = RichHandler(
formatter = logging.Formatter("%(message)s")
⋮----
# Remove any existing handlers to avoid duplicates on reconfiguration

================
File: src/fastmcp/utilities/mcp_config.py
================
"""
    Infer the appropriate transport type from the given URL.
    """
url = str(url)
⋮----
parsed_url = urlparse(url)
path = parsed_url.path
# Match /sse followed by /, ?, &, or end of string
⋮----
class StdioMCPServer(FastMCPBaseModel)
⋮----
command: str
args: list[str] = Field(default_factory=list)
env: dict[str, Any] = Field(default_factory=dict)
cwd: str | None = None
transport: Literal["stdio"] = "stdio"
def to_transport(self) -> StdioTransport
class RemoteMCPServer(FastMCPBaseModel)
⋮----
url: str
headers: dict[str, str] = Field(default_factory=dict)
transport: Literal["http", "streamable-http", "sse"] | None = None
auth: Annotated[
model_config = ConfigDict(arbitrary_types_allowed=True)
def to_transport(self) -> StreamableHttpTransport | SSETransport
⋮----
transport = infer_transport_type_from_url(self.url)
⋮----
transport = self.transport
⋮----
# Both "http" and "streamable-http" map to StreamableHttpTransport
⋮----
class MCPConfig(FastMCPBaseModel)
⋮----
mcpServers: dict[str, StdioMCPServer | RemoteMCPServer]
⋮----
@classmethod
    def from_dict(cls, config: dict[str, Any]) -> MCPConfig

================
File: src/fastmcp/utilities/openapi.py
================
# Import OpenAPI 3.0 models as well
⋮----
logger = logging.getLogger(__name__)
# --- Intermediate Representation (IR) Definition ---
# (IR models remain the same)
HttpMethod = Literal[
ParameterLocation = Literal["path", "query", "header", "cookie"]
JsonSchema = dict[str, Any]
class ParameterInfo(FastMCPBaseModel)
⋮----
"""Represents a single parameter for an HTTP operation in our IR."""
name: str
location: ParameterLocation  # Mapped from 'in' field of openapi-pydantic Parameter
required: bool = False
schema_: JsonSchema = Field(..., alias="schema")  # Target name in IR
description: str | None = None
class RequestBodyInfo(FastMCPBaseModel)
⋮----
"""Represents the request body for an HTTP operation in our IR."""
⋮----
content_schema: dict[str, JsonSchema] = Field(
⋮----
)  # Key: media type
⋮----
class ResponseInfo(FastMCPBaseModel)
⋮----
"""Represents response information in our IR."""
⋮----
# Store schema per media type, key is media type
content_schema: dict[str, JsonSchema] = Field(default_factory=dict)
class HTTPRoute(FastMCPBaseModel)
⋮----
"""Intermediate Representation for a single OpenAPI operation."""
path: str
method: HttpMethod
operation_id: str | None = None
summary: str | None = None
⋮----
tags: list[str] = Field(default_factory=list)
parameters: list[ParameterInfo] = Field(default_factory=list)
request_body: RequestBodyInfo | None = None
responses: dict[str, ResponseInfo] = Field(
⋮----
)  # Key: status code str
schema_definitions: dict[str, JsonSchema] = Field(
⋮----
)  # Store component schemas
extensions: dict[str, Any] = Field(default_factory=dict)
# Export public symbols
__all__ = [
# Type variables for generic parser
TOpenAPI = TypeVar("TOpenAPI", OpenAPI, OpenAPI_30)
TSchema = TypeVar("TSchema", Schema, Schema_30)
TReference = TypeVar("TReference", Reference, Reference_30)
TParameter = TypeVar("TParameter", Parameter, Parameter_30)
TRequestBody = TypeVar("TRequestBody", RequestBody, RequestBody_30)
TResponse = TypeVar("TResponse", Response, Response_30)
TOperation = TypeVar("TOperation", Operation, Operation_30)
TPathItem = TypeVar("TPathItem", PathItem, PathItem_30)
def parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute]
⋮----
"""
    Parses an OpenAPI schema dictionary into a list of HTTPRoute objects
    using the openapi-pydantic library.
    Supports both OpenAPI 3.0.x and 3.1.x versions.
    """
# Check OpenAPI version to use appropriate model
openapi_version = openapi_dict.get("openapi", "")
⋮----
# Use OpenAPI 3.0 models
openapi_30 = OpenAPI_30.model_validate(openapi_dict)
⋮----
parser = OpenAPIParser(
⋮----
# Default to OpenAPI 3.1 models
openapi_31 = OpenAPI.model_validate(openapi_dict)
⋮----
error_details = e.errors()
⋮----
class OpenAPIParser(
⋮----
"""Unified parser for OpenAPI schemas with generic type parameters to handle both 3.0 and 3.1."""
⋮----
"""Initialize the parser with the OpenAPI schema and type classes."""
⋮----
def _convert_to_parameter_location(self, param_in: str) -> ParameterLocation
⋮----
"""Convert string parameter location to our ParameterLocation type."""
⋮----
return param_in  # type: ignore[return-value]  # Safe cast since we checked values
⋮----
return "query"  # type: ignore[return-value]  # Safe cast to default value
def _resolve_ref(self, item: Any) -> Any
⋮----
"""Resolves a reference to its target definition."""
⋮----
ref_str = item.ref
⋮----
parts = ref_str.strip("#/").split("/")
target = self.openapi
⋮----
target = target[int(part)]
⋮----
# Check class fields first, then model_extra
⋮----
target = getattr(target, part, None)
⋮----
target = target.model_extra[part]
⋮----
# Special handling for components
⋮----
target = getattr(target, "components")
elif hasattr(target, part):  # Fallback check
⋮----
target = None  # Part not found
⋮----
target = target.get(part)
⋮----
# Handle nested references
⋮----
def _extract_schema_as_dict(self, schema_obj: Any) -> JsonSchema
⋮----
"""Resolves a schema and returns it as a dictionary."""
⋮----
resolved_schema = self._resolve_ref(schema_obj)
⋮----
# Convert schema to dictionary
result = resolved_schema.model_dump(
⋮----
result = resolved_schema
⋮----
result = {}
⋮----
# Re-raise ValueError for external reference errors and other validation issues
⋮----
"""Extract and resolve parameters from operation and path item."""
extracted_params: list[ParameterInfo] = []
seen_params: dict[
⋮----
] = {}  # Use tuple of (name, location) as key
all_params = (operation_params or []) + (path_item_params or [])
⋮----
parameter = self._resolve_ref(param_or_ref)
⋮----
# Extract parameter info - handle both 3.0 and 3.1 parameter models
param_in = parameter.param_in  # Both use param_in
# Handle enum or string parameter locations
⋮----
param_in_str = (
param_location = self._convert_to_parameter_location(param_in_str)
param_schema_obj = parameter.param_schema  # Both use param_schema
# Skip duplicate parameters (same name and location)
param_key = (parameter.name, param_in_str)
⋮----
# Extract schema
param_schema_dict = {}
⋮----
# Process schema object
param_schema_dict = self._extract_schema_as_dict(param_schema_obj)
# Handle default value
resolved_schema = self._resolve_ref(param_schema_obj)
⋮----
# Handle content-based parameters
first_media_type = next(iter(parameter.content.values()), None)
⋮----
media_schema = first_media_type.media_type_schema
param_schema_dict = self._extract_schema_as_dict(media_schema)
# Handle default value in content schema
resolved_media_schema = self._resolve_ref(media_schema)
⋮----
# Create parameter info object
param_info = ParameterInfo(
⋮----
param_name = getattr(
⋮----
def _extract_request_body(self, request_body_or_ref: Any) -> RequestBodyInfo | None
⋮----
"""Extract and resolve request body information."""
⋮----
request_body = self._resolve_ref(request_body_or_ref)
⋮----
# Create request body info
request_body_info = RequestBodyInfo(
# Extract content schemas
⋮----
schema_dict = self._extract_schema_as_dict(
⋮----
# Re-raise ValueError for external reference errors
⋮----
# Re-raise ValueError for external reference errors
⋮----
ref_name = getattr(request_body_or_ref, "ref", "unknown")
⋮----
"""Extract and resolve response information."""
extracted_responses: dict[str, ResponseInfo] = {}
⋮----
response = self._resolve_ref(resp_or_ref)
⋮----
# Create response info
resp_info = ResponseInfo(description=response.description)
# Extract content schemas
⋮----
# Re-raise ValueError for external reference errors
⋮----
# Re-raise ValueError for external reference errors
⋮----
ref_name = getattr(resp_or_ref, "ref", "unknown")
⋮----
def parse(self) -> list[HTTPRoute]
⋮----
"""Parse the OpenAPI schema into HTTP routes."""
routes: list[HTTPRoute] = []
⋮----
# Extract component schemas
schema_definitions = {}
⋮----
components = self.openapi.components
⋮----
resolved_schema = self._resolve_ref(schema)
⋮----
# Process paths and operations
⋮----
path_level_params = (
# Get HTTP methods from the path item class fields
http_methods = [
⋮----
operation = getattr(path_item_obj, method_lower, None)
⋮----
# Cast method to HttpMethod - safe since we only use valid HTTP methods
method_upper = method_lower.upper()
⋮----
parameters = self._extract_parameters(
request_body_info = self._extract_request_body(
responses = self._extract_responses(
extensions = {}
⋮----
extensions = {
route = HTTPRoute(
⋮----
method=method_upper,  # type: ignore[arg-type]  # Known valid HTTP method
⋮----
# Re-raise ValueError for external reference errors
⋮----
op_id = getattr(operation, "operationId", "unknown")
⋮----
def clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None
⋮----
"""
    Clean up a schema dictionary for display by removing internal/complex fields.
    """
⋮----
# Make a copy to avoid modifying the input schema
cleaned = schema.copy()
# Fields commonly removed for simpler display to LLMs or users
fields_to_remove = [
⋮----
"not",  # Composition keywords
"nullable",  # Handled by type unions usually
⋮----
# Can be verbose, maybe remove based on flag?
# "pattern", "minLength", "maxLength",
# "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
# "multipleOf", "minItems", "maxItems", "uniqueItems",
# "minProperties", "maxProperties"
⋮----
# Recursively clean properties and items
⋮----
# Remove properties section if empty after cleaning
⋮----
# Remove items section if empty after cleaning
⋮----
# Often verbose, can be simplified
⋮----
# Maybe keep 'true' or represent as 'Allows additional properties' text?
pass  # Keep simple boolean for now
def generate_example_from_schema(schema: JsonSchema | None) -> Any
⋮----
"""
    Generate a simple example value from a JSON schema dictionary.
    Very basic implementation focusing on types.
    """
⋮----
return "unknown"  # Or None?
# Use default value if provided
⋮----
# Use first enum value if provided
⋮----
# Use first example if provided
⋮----
schema_type = schema.get("type")
⋮----
properties = schema.get("properties", {})
⋮----
# Generate example for first few properties or required ones? Limit complexity.
required_props = set(schema.get("required", []))
props_to_include = list(properties.keys())[
⋮----
]  # Limit to first 3 for brevity
⋮----
# Ensure required props are present if possible
⋮----
return result if result else {"key": "value"}  # Basic object if no props
⋮----
items_schema = schema.get("items")
⋮----
# Generate one example item
item_example = generate_example_from_schema(items_schema)
⋮----
return ["example_item"]  # Fallback
⋮----
format_type = schema.get("format")
⋮----
return "ZXhhbXBsZQ=="  # "example" base64
⋮----
# Fallback if type is unknown or missing
⋮----
def format_json_for_description(data: Any, indent: int = 2) -> str
⋮----
"""Formats Python data as a JSON string block for markdown."""
⋮----
json_str = json.dumps(data, indent=indent)
⋮----
],  # Changed from specific ResponseInfo type to avoid circular imports
parameters: list[ParameterInfo] | None = None,  # Add parameters parameter
request_body: RequestBodyInfo | None = None,  # Add request_body parameter
⋮----
"""
    Formats the base description string with response, parameter, and request body information.
    Args:
        base_description (str): The initial description to be formatted.
        responses (dict[str, Any]): A dictionary of response information, keyed by status code.
        parameters (list[ParameterInfo] | None, optional): A list of parameter information,
            including path and query parameters. Each parameter includes details such as name,
            location, whether it is required, and a description.
        request_body (RequestBodyInfo | None, optional): Information about the request body,
            including its description, whether it is required, and its content schema.
    Returns:
        str: The formatted description string with additional details about responses, parameters,
        and the request body.
    """
desc_parts = [base_description]
# Add parameter information
⋮----
# Process path parameters
path_params = [p for p in parameters if p.location == "path"]
⋮----
param_section = "\n\n**Path Parameters:**"
⋮----
required_marker = " (Required)" if param.required else ""
param_desc = f"\n- **{param.name}**{required_marker}: {param.description or 'No description.'}"
⋮----
# Process query parameters
query_params = [p for p in parameters if p.location == "query"]
⋮----
param_section = "\n\n**Query Parameters:**"
⋮----
# Add request body information if present
⋮----
req_body_section = "\n\n**Request Body:**"
⋮----
required_marker = " (Required)" if request_body.required else ""
⋮----
# Add request body property descriptions if available
⋮----
media_type = (
⋮----
schema = request_body.content_schema.get(media_type, {})
⋮----
required = prop_name in schema.get("required", [])
req_mark = " (Required)" if required else ""
⋮----
# Add response information
⋮----
response_section = "\n\n**Responses:**"
added_response_section = False
# Determine success codes (common ones)
success_codes = {"200", "201", "202", "204"}  # As strings
success_status = next((s for s in success_codes if s in responses), None)
# Process all responses
responses_to_process = responses.items()
⋮----
added_response_section = True
status_marker = " (Success)" if status_code == success_status else ""
⋮----
# Process content schemas for this response
⋮----
# Prioritize json, then take first available
⋮----
schema = resp_info.content_schema.get(media_type)
⋮----
# Add response property descriptions
⋮----
# Handle array responses
⋮----
items_schema = schema["items"]
⋮----
# Handle object responses
⋮----
# Generate Example
⋮----
example = generate_example_from_schema(schema)
⋮----
"""
    Replace openapi $ref with jsonschema $defs
    Examples:
    - {"type": "object", "properties": {"$ref": "#/components/schemas/..."}}
    - {"$ref": "#/components/schemas/..."}
    - {"items": {"$ref": "#/components/schemas/..."}}
    - {"anyOf": [{"$ref": "#/components/schemas/..."}]}
    - {"allOf": [{"$ref": "#/components/schemas/..."}]}
    - {"oneOf": [{"$ref": "#/components/schemas/..."}]}
    Args:
        info: dict[str, Any]
        description: str | None
    Returns:
        dict[str, Any]
    """
schema = info.copy()
⋮----
schema_name = ref_path.split("/")[-1]
⋮----
def _combine_schemas(route: HTTPRoute) -> dict[str, Any]
⋮----
"""
    Combines parameter and request body schemas into a single schema.
    Args:
        route: HTTPRoute object
    Returns:
        Combined schema dictionary
    """
properties = {}
required = []
# Add path parameters
⋮----
# Add request body if it exists
⋮----
# For now, just use the first content type's schema
content_type = next(iter(route.request_body.content_schema))
body_schema = _replace_ref_with_defs(
body_props = body_schema.get("properties", {})
# Add request body properties
⋮----
result = {
# Add schema definitions if available
⋮----
# Use compress_schema to remove unused definitions
result = compress_schema(result)

================
File: src/fastmcp/utilities/tests.py
================
@contextmanager
def temporary_settings(**kwargs: Any)
⋮----
"""
    Temporarily override FastMCP setting values.
    Args:
        **kwargs: The settings to override, including nested settings.
    Example:
        Temporarily override a setting:
        ```python
        import fastmcp
        from fastmcp.utilities.tests import temporary_settings
        with temporary_settings(log_level='DEBUG'):
            assert fastmcp.settings.log_level == 'DEBUG'
        assert fastmcp.settings.log_level == 'INFO'
        ```
    """
old_settings = copy.deepcopy(settings.model_dump())
⋮----
# apply the new settings
⋮----
# restore the old settings
⋮----
def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> None
⋮----
# Some Starlette apps are not pickleable, so we need to create them here based on the indicated transport
⋮----
app = mcp_server.http_app(transport="sse")
⋮----
uvicorn_server = uvicorn.Server(
⋮----
"""
    Context manager that runs a FastMCP server in a separate process and
    returns the server URL. When the context manager is exited, the server process is killed.
    Args:
        server_fn: The function that runs a FastMCP server. FastMCP servers are
            not pickleable, so we need a function that creates and runs one.
        *args: Arguments to pass to the server function.
        provide_host_and_port: Whether to provide the host and port to the server function as kwargs.
        **kwargs: Keyword arguments to pass to the server function.
    Returns:
        The server URL.
    """
host = "127.0.0.1"
port = find_available_port()
⋮----
proc = multiprocessing.Process(
⋮----
# Wait for server to be running
max_attempts = 10
attempt = 0
⋮----
# If it's still alive, then force kill it

================
File: src/fastmcp/utilities/types.py
================
"""Common types used across FastMCP."""
⋮----
T = TypeVar("T")
# sentinel values for optional arguments
NotSet = ...
NotSetT: TypeAlias = EllipsisType
class FastMCPBaseModel(BaseModel)
⋮----
"""Base model for FastMCP models."""
model_config = ConfigDict(extra="forbid")
⋮----
@lru_cache(maxsize=5000)
def get_cached_typeadapter(cls: T) -> TypeAdapter[T]
⋮----
"""
    TypeAdapters are heavy objects, and in an application context we'd typically
    create them once in a global scope and reuse them as often as possible.
    However, this isn't feasible for user-generated functions. Instead, we use a
    cache to minimize the cost of creating them as much as possible.
    """
⋮----
def issubclass_safe(cls: type, base: type) -> bool
⋮----
"""Check if cls is a subclass of base, even if cls is a type variable."""
⋮----
def is_class_member_of_type(cls: type, base: type) -> bool
⋮----
"""
    Check if cls is a member of base, even if cls is a type variable.
    Base can be a type, a UnionType, or an Annotated type. Generic types are not
    considered members (e.g. T is not a member of list[T]).
    """
origin = get_origin(cls)
# Handle both types of unions: UnionType (from types module, used with | syntax)
# and typing.Union (used with Union[] syntax)
⋮----
# For Annotated[T, ...], check if T is a member of base
args = get_args(cls)
⋮----
def find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None
⋮----
"""
    Find the name of the kwarg that is of type kwarg_type.
    Includes union types that contain the kwarg_type, as well as Annotated types.
    """
⋮----
sig = inspect.signature(fn.__func__)
⋮----
sig = inspect.signature(fn)
⋮----
class Image
⋮----
"""Helper class for returning images from tools."""
⋮----
def _get_mime_type(self) -> str
⋮----
"""Get MIME type from format or guess from file extension."""
⋮----
suffix = self.path.suffix.lower()
⋮----
return "image/png"  # default for raw binary data
⋮----
"""Convert to MCP ImageContent."""
⋮----
data = base64.b64encode(f.read()).decode()
⋮----
data = base64.b64encode(self.data).decode()
⋮----
class Audio
⋮----
"""Helper class for returning audio from tools."""
⋮----
return "audio/wav"  # default for raw binary data
⋮----
class File
⋮----
fmt = self._format.lower()
# Map common text formats to text/plain
⋮----
raw_data = f.read()
uri_str = self.path.resolve().as_uri()
⋮----
raw_data = self.data
⋮----
uri_str = f"file:///{self._name}.{self._mime_type.split('/')[1]}"
⋮----
uri_str = f"file:///resource.{self._mime_type.split('/')[1]}"
⋮----
mime = mime_type or self._mime_type
UriType = Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri = TypeAdapter(UriType).validate_python(uri_str)
⋮----
text = raw_data.decode("utf-8")
⋮----
text = raw_data.decode("latin-1")
resource = mcp.types.TextResourceContents(
⋮----
data = base64.b64encode(raw_data).decode()
resource = mcp.types.BlobResourceContents(
⋮----
def replace_type(type_, type_map: dict[type, type])
⋮----
"""
    Given a (possibly generic, nested, or otherwise complex) type, replaces all
    instances of old_type with new_type.
    This is useful for transforming types when creating tools.
    Args:
        type_: The type to replace instances of old_type with new_type.
        old_type: The type to replace.
        new_type: The type to replace old_type with.
    Examples:
        >>> replace_type(list[int | bool], {int: str})
        list[str | bool]
        >>> replace_type(list[list[int]], {int: str})
        list[list[str]]
    """
⋮----
origin = get_origin(type_)
⋮----
args = get_args(type_)
new_args = tuple(replace_type(arg, type_map) for arg in args)
⋮----
return Union[new_args]  # type: ignore # noqa: UP007

================
File: src/fastmcp/__init__.py
================
"""FastMCP - An ergonomic MCP interface."""
⋮----
settings = Settings()
⋮----
__version__ = version("fastmcp")
# ensure deprecation warnings are displayed by default
⋮----
def __getattr__(name: str)
⋮----
"""
    Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
    """
⋮----
# Deprecated in 2.8.1
⋮----
__all__ = [

================
File: src/fastmcp/exceptions.py
================
"""Custom exceptions for FastMCP."""
from mcp import McpError  # noqa: F401
class FastMCPError(Exception)
⋮----
"""Base error for FastMCP."""
class ValidationError(FastMCPError)
⋮----
"""Error in validating parameters or return values."""
class ResourceError(FastMCPError)
⋮----
"""Error in resource operations."""
class ToolError(FastMCPError)
⋮----
"""Error in tool operations."""
class PromptError(FastMCPError)
⋮----
"""Error in prompt operations."""
class InvalidSignature(Exception)
⋮----
"""Invalid signature for use with FastMCP."""
class ClientError(Exception)
⋮----
"""Error in client operations."""
class NotFoundError(Exception)
⋮----
"""Object not found."""
class DisabledError(Exception)
⋮----
"""Object is disabled."""

================
File: src/fastmcp/settings.py
================
logger = get_logger(__name__)
LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
class ExtendedEnvSettingsSource(EnvSettingsSource)
⋮----
"""
    A special EnvSettingsSource that allows for multiple env var prefixes to be used.
    Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
    """
⋮----
# Deprecated in 2.8.0
⋮----
class ExtendedSettingsConfigDict(SettingsConfigDict, total=False)
⋮----
env_prefixes: list[str] | None
class Settings(BaseSettings)
⋮----
"""FastMCP settings."""
model_config = ExtendedSettingsConfigDict(
⋮----
# can remove this classmethod after deprecated FASTMCP_SERVER_ prefix is
# removed
⋮----
@property
    def settings(self) -> Self
⋮----
"""
        This property is for backwards compatibility with FastMCP < 2.8.0,
        which accessed fastmcp.settings.settings
        """
# Deprecated in 2.8.0
⋮----
home: Path = Path.home() / ".fastmcp"
test_mode: bool = False
log_level: LOG_LEVEL = "INFO"
enable_rich_tracebacks: Annotated[
deprecation_warnings: Annotated[
client_raise_first_exceptiongroup_error: Annotated[
resource_prefix_format: Annotated[
client_init_timeout: Annotated[
⋮----
@model_validator(mode="after")
    def setup_logging(self) -> Self
⋮----
"""Finalize the settings."""
⋮----
# HTTP settings
host: str = "127.0.0.1"
port: int = 8000
sse_path: str = "/sse/"
message_path: str = "/messages/"
streamable_http_path: str = "/mcp/"
debug: bool = False
# error handling
mask_error_details: Annotated[
server_dependencies: Annotated[
# StreamableHTTP settings
json_response: bool = False
stateless_http: bool = (
⋮----
False  # If True, uses true stateless mode (new transport per request)
⋮----
# Auth settings
default_auth_provider: Annotated[
include_tags: Annotated[
exclude_tags: Annotated[
settings = Settings()

================
File: tests/auth/providers/test_bearer_env.py
================
def test_load_bearer_env_from_env_var(monkeypatch)
⋮----
mcp = FastMCP()
⋮----
mcp_with_auth = FastMCP()
⋮----
def test_load_bearer_env_from_env_var_requires_public_key_or_jwks_uri(monkeypatch)
def test_configure_bearer_env_from_env_var(monkeypatch)
def test_list_of_scopes_must_be_a_list(monkeypatch)
def test_configure_bearer_env_jwks_uri_from_env_var(monkeypatch)
def test_configure_bearer_env_public_key_and_jwks_uri_error(monkeypatch)
def test_provided_auth_takes_precedence_over_env_vars(monkeypatch)
⋮----
mcp = FastMCP(auth=BearerAuthProvider(public_key="test-public-key-2"))

================
File: tests/auth/providers/test_bearer.py
================
@pytest.fixture(scope="module")
def rsa_key_pair() -> RSAKeyPair
⋮----
@pytest.fixture(scope="module")
def bearer_token(rsa_key_pair: RSAKeyPair) -> str
⋮----
@pytest.fixture
def bearer_provider(rsa_key_pair: RSAKeyPair) -> BearerAuthProvider
⋮----
mcp = FastMCP(
⋮----
@mcp.tool
    def add(a: int, b: int) -> int
⋮----
@pytest.fixture(scope="module")
def mcp_server_url(rsa_key_pair: RSAKeyPair) -> Generator[str]
class TestRSAKeyPair
⋮----
def test_generate_key_pair(self)
⋮----
"""Test RSA key pair generation."""
key_pair = RSAKeyPair.generate()
⋮----
# Check that keys are in PEM format
private_pem = key_pair.private_key.get_secret_value()
public_pem = key_pair.public_key
⋮----
def test_create_basic_token(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test basic token creation."""
token = rsa_key_pair.create_token(
⋮----
assert len(token.split(".")) == 3  # JWT has 3 parts
def test_create_token_with_scopes(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test token creation with scopes."""
⋮----
# We'll validate the scopes in the BearerToken tests
class TestBearerTokenJWKS
⋮----
"""Tests for JWKS URI functionality."""
⋮----
@pytest.fixture
    def jwks_provider(self, rsa_key_pair: RSAKeyPair) -> BearerAuthProvider
⋮----
"""Provider configured with JWKS URI."""
⋮----
@pytest.fixture
    def mock_jwks_data(self, rsa_key_pair: RSAKeyPair) -> JWKSData
⋮----
"""Create mock JWKS data from RSA key pair."""
⋮----
# Create JWK from the RSA public key
jwk = JsonWebKey.import_key(rsa_key_pair.public_key)  # type: ignore
jwk_data: JWKData = jwk.as_dict()  # type: ignore
⋮----
"""Test token validation using JWKS URI."""
⋮----
access_token = await jwks_provider.load_access_token(token)
⋮----
token = RSAKeyPair.generate().create_token(
⋮----
class TestBearerToken
⋮----
def test_initialization_with_public_key(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test provider initialization with public key."""
provider = BearerAuthProvider(
⋮----
def test_initialization_with_jwks_uri(self)
⋮----
"""Test provider initialization with JWKS URI."""
⋮----
def test_initialization_requires_key_or_uri(self)
⋮----
"""Test that either public_key or jwks_uri is required."""
⋮----
def test_initialization_rejects_both_key_and_uri(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that both public_key and jwks_uri cannot be provided."""
⋮----
"""Test validation of a valid token."""
⋮----
access_token = await bearer_provider.load_access_token(token)
⋮----
"""Test rejection of expired tokens."""
⋮----
expires_in_seconds=-3600,  # Expired 1 hour ago
⋮----
"""Test rejection of tokens with invalid issuer."""
⋮----
issuer="https://evil.example.com",  # Wrong issuer
⋮----
"""Test rejection of tokens with invalid audience."""
⋮----
audience="https://wrong-api.example.com",  # Wrong audience
⋮----
async def test_no_issuer_validation_when_none(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that issuer validation is skipped when provider has no issuer configured."""
⋮----
issuer=None,  # No issuer validation
⋮----
access_token = await provider.load_access_token(token)
⋮----
async def test_no_audience_validation_when_none(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that audience validation is skipped when provider has no audience configured."""
⋮----
audience=None,  # No audience validation
⋮----
async def test_multiple_audiences_validation(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test validation with multiple audiences in token."""
⋮----
"""Test provider configured with multiple expected audiences."""
⋮----
# Token with single audience that matches one of the expected
token1 = rsa_key_pair.create_token(
access_token1 = await provider.load_access_token(token1)
⋮----
# Token with multiple audiences, one of which matches
token2 = rsa_key_pair.create_token(
access_token2 = await provider.load_access_token(token2)
⋮----
# Token with audience that doesn't match any expected
token3 = rsa_key_pair.create_token(
access_token3 = await provider.load_access_token(token3)
⋮----
"""Test scope extraction from space-separated string."""
⋮----
"""Test scope extraction from list format."""
⋮----
additional_claims={"scope": ["read", "write"]},  # List format
⋮----
"""Test token with no scopes."""
⋮----
# No scopes
⋮----
async def test_malformed_token_rejection(self, bearer_provider: BearerAuthProvider)
⋮----
"""Test rejection of malformed tokens."""
malformed_tokens = [
⋮----
"header.body",  # Missing signature
⋮----
"""Test rejection of tokens with invalid signatures."""
# Create a token with a different key pair
other_key_pair = RSAKeyPair.generate()
token = other_key_pair.create_token(
⋮----
"""Test client_id extraction with fallback logic."""
# Test with explicit client_id claim
⋮----
assert access_token.client_id == "app456"  # Should prefer client_id over sub
async def test_string_issuer_validation(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that string (non-URL) issuers are supported per RFC 7519."""
# Create provider with string issuer
⋮----
issuer="my-service",  # String issuer, not a URL
⋮----
# Create token with matching string issuer
⋮----
issuer="my-service",  # Same string issuer
⋮----
async def test_string_issuer_mismatch_rejection(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that mismatched string issuers are rejected."""
# Create provider with one string issuer
⋮----
# Create token with different string issuer
⋮----
issuer="other-service",  # Different string issuer
⋮----
async def test_url_issuer_still_works(self, rsa_key_pair: RSAKeyPair)
⋮----
"""Test that URL issuers still work after the fix."""
# Create provider with URL issuer
⋮----
issuer="https://my-auth-server.com",  # URL issuer
⋮----
# Create token with matching URL issuer
⋮----
issuer="https://my-auth-server.com",  # Same URL issuer
⋮----
class TestFastMCPBearerAuth
⋮----
def test_bearer_auth(self)
async def test_unauthorized_access(self, mcp_server_url: str)
⋮----
tools = await client.list_tools()  # noqa: F841
⋮----
async def test_authorized_access(self, mcp_server_url: str, bearer_token)
⋮----
tools = await client.list_tools()  # noqa: F841
⋮----
async def test_invalid_token_raises_401(self, mcp_server_url: str)
async def test_expired_token(self, mcp_server_url: str, rsa_key_pair: RSAKeyPair)
async def test_token_with_bad_signature(self, mcp_server_url: str)
⋮----
rsa_key_pair = RSAKeyPair.generate()
token = rsa_key_pair.create_token()
⋮----
mcp_server_url = f"{url}/mcp/"
⋮----
tools = await client.list_tools()  # noqa: F841
⋮----
tools = await client.list_tools()

================
File: tests/auth/providers/test_token_verifier.py
================
"""Tests for TokenVerifier protocol implementation in auth providers."""
⋮----
class TestBearerAuthProviderTokenVerifier
⋮----
"""Test that BearerAuthProvider implements TokenVerifier protocol correctly."""
⋮----
@pytest.fixture
    def rsa_key_pair(self) -> RSAKeyPair
⋮----
"""Generate RSA key pair for testing."""
⋮----
@pytest.fixture
    def bearer_provider(self, rsa_key_pair: RSAKeyPair) -> BearerAuthProvider
⋮----
"""Create BearerAuthProvider for testing."""
⋮----
@pytest.fixture
    def valid_token(self, rsa_key_pair: RSAKeyPair) -> str
⋮----
"""Create a valid test token."""
⋮----
@pytest.fixture
    def expired_token(self, rsa_key_pair: RSAKeyPair) -> str
⋮----
"""Create an expired test token."""
⋮----
expires_in_seconds=-3600,  # Expired 1 hour ago
⋮----
"""Test that verify_token returns AccessToken for valid token."""
result = await bearer_provider.verify_token(valid_token)
⋮----
"""Test that verify_token returns None for expired token."""
result = await bearer_provider.verify_token(expired_token)
⋮----
"""Test that verify_token returns None for invalid token."""
result = await bearer_provider.verify_token("invalid.token.here")
⋮----
"""Test that verify_token returns None for malformed token."""
result = await bearer_provider.verify_token("not-a-jwt")
⋮----
"""Test that verify_token delegates to load_access_token."""
# Both methods should return the same result
verify_result = await bearer_provider.verify_token(valid_token)
load_result = await bearer_provider.load_access_token(valid_token)
⋮----
class TestInMemoryOAuthProviderTokenVerifier
⋮----
"""Test that InMemoryOAuthProvider implements TokenVerifier protocol correctly."""
⋮----
@pytest.fixture
    def in_memory_provider(self) -> InMemoryOAuthProvider
⋮----
"""Create InMemoryOAuthProvider for testing."""
⋮----
"""Test that verify_token returns None for nonexistent token."""
result = await in_memory_provider.verify_token("nonexistent-token")
⋮----
# Create a test token in the provider's storage
test_token = "test-access-token"
test_access_token = AccessToken(
⋮----
expires_at=None,  # No expiry
⋮----
verify_result = await in_memory_provider.verify_token(test_token)
load_result = await in_memory_provider.load_access_token(test_token)
⋮----
# Create an expired token
expired_token = "expired-token"
expired_access_token = AccessToken(
⋮----
expires_at=int(time.time()) - 3600,  # Expired 1 hour ago
⋮----
result = await in_memory_provider.verify_token(expired_token)
⋮----
# Token should be cleaned up from storage
⋮----
class TestTokenVerifierProtocolCompliance
⋮----
"""Test that our providers properly implement the TokenVerifier protocol."""
async def test_bearer_provider_implements_protocol(self)
⋮----
"""Test that BearerAuthProvider can be used as TokenVerifier."""
key_pair = RSAKeyPair.generate()
provider = BearerAuthProvider(public_key=key_pair.public_key)
# Should have the required method for TokenVerifier protocol
⋮----
async def test_in_memory_provider_implements_protocol(self)
⋮----
"""Test that InMemoryOAuthProvider can be used as TokenVerifier."""
provider = InMemoryOAuthProvider()

================
File: tests/auth/test_oauth_client.py
================
import fastmcp.client.auth.oauth  # Import module, not the function directly
⋮----
def fastmcp_server(issuer_url: str)
⋮----
"""Create a FastMCP server with OAuth authentication."""
server = FastMCP(
⋮----
@server.tool
    def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.resource("resource://test")
    def get_test_resource() -> str
⋮----
"""Get a test resource."""
⋮----
def run_server(host: str, port: int, **kwargs) -> None
⋮----
@pytest.fixture(scope="module")
def streamable_http_server() -> Generator[str, None, None]
⋮----
@pytest.fixture()
def client_unauthorized(streamable_http_server: str) -> Client
class HeadlessOAuthProvider(httpx.Auth)
⋮----
"""
    OAuth provider that bypasses browser interaction for testing.
    This simulates the complete OAuth flow programmatically by:
    1. Discovering OAuth metadata from the server
    2. Registering a client
    3. Getting an authorization code (simulates user approval)
    4. Exchanging it for an access token
    5. Adding Bearer token to all requests
    This enables testing OAuth-protected FastMCP servers without
    requiring browser interaction or external OAuth providers.
    """
def __init__(self, mcp_url: str)
⋮----
parsed_url = urlparse(mcp_url)
⋮----
async def async_auth_flow(self, request)
⋮----
"""httpx.Auth interface - add Bearer token to requests."""
⋮----
async def _obtain_token(self)
⋮----
"""Get a valid access token by simulating the OAuth flow."""
⋮----
# Generate PKCE challenge/verifier
code_verifier = (
code_challenge = (
# Create HTTP client to talk to the server
⋮----
# 1. Discover OAuth metadata
metadata_url = (
response = await http_client.get(metadata_url)
⋮----
metadata = response.json()
# 2. Register a client
client_info = OAuthClientInformationFull(
register_response = await http_client.post(
⋮----
registered_client = register_response.json()
# 3. Get authorization code (simulate user approval)
auth_params = {
auth_response = await http_client.get(
# Extract auth code from redirect
⋮----
redirect_url = auth_response.headers["location"]
parsed = urlparse(redirect_url)
query_params = parse_qs(parsed.query)
⋮----
error = query_params["error"][0]
error_desc = query_params.get(
⋮----
auth_code = query_params["code"][0]
# 4. Exchange auth code for access token
token_data = {
token_response = await http_client.post(
⋮----
token_info = token_response.json()
⋮----
"""Client with headless OAuth that bypasses browser interaction."""
# Patch the OAuth function to return our headless provider
def headless_oauth(*args, **kwargs)
⋮----
mcp_url = args[0] if args else kwargs.get("mcp_url", "")
⋮----
client = Client(
⋮----
async def test_unauthorized(client_unauthorized: Client)
⋮----
"""Test that unauthenticated requests are rejected."""
⋮----
async def test_ping(client_with_headless_oauth: Client)
⋮----
"""Test that we can ping the server."""
⋮----
async def test_list_tools(client_with_headless_oauth: Client)
⋮----
"""Test that we can list tools."""
⋮----
tools = await client_with_headless_oauth.list_tools()
tool_names = [tool.name for tool in tools]
⋮----
async def test_call_tool(client_with_headless_oauth: Client)
⋮----
"""Test that we can call a tool."""
⋮----
result = await client_with_headless_oauth.call_tool("add", {"a": 5, "b": 3})
# The add tool returns int which gets wrapped as structured output
# Client unwraps it and puts the actual int in the data field
⋮----
async def test_list_resources(client_with_headless_oauth: Client)
⋮----
"""Test that we can list resources."""
⋮----
resources = await client_with_headless_oauth.list_resources()
resource_uris = [str(resource.uri) for resource in resources]
⋮----
async def test_read_resource(client_with_headless_oauth: Client)
⋮----
"""Test that we can read a resource."""
⋮----
resource = await client_with_headless_oauth.read_resource("resource://test")
assert resource[0].text == "Hello from authenticated resource!"  # type: ignore[attr-defined]
async def test_oauth_server_metadata_discovery(streamable_http_server: str)
⋮----
"""Test that we can discover OAuth metadata from the running server."""
parsed_url = urlparse(streamable_http_server)
server_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
⋮----
# Test OAuth discovery endpoint
metadata_url = f"{server_base_url}/.well-known/oauth-authorization-server"
response = await client.get(metadata_url)
⋮----
# The endpoints should be properly formed URLs

================
File: tests/cli/test_cli.py
================
"""Tests for the CLI module."""
⋮----
# Set up test runner
runner = CliRunner()
⋮----
@pytest.fixture
def mock_console()
⋮----
"""Mock the rich console to test output."""
⋮----
@pytest.fixture
def mock_logger()
⋮----
"""Mock the logger to test logging."""
⋮----
@pytest.fixture
def mock_exit()
⋮----
"""Mock sys.exit to prevent tests from exiting."""
⋮----
@pytest.fixture
def temp_python_file(tmp_path)
⋮----
"""Create a temporary Python file with a test server."""
server_code = """
file_path = tmp_path / "test_server.py"
⋮----
@pytest.fixture
def temp_env_file(tmp_path)
⋮----
"""Create a temporary .env file."""
env_content = """
env_path = tmp_path / ".env"
⋮----
class TestHelperFunctions
⋮----
"""Tests for helper functions in cli.py."""
def test_get_npx_command_unix(self)
⋮----
"""Test getting npx command on unix systems."""
⋮----
def test_get_npx_command_windows(self)
⋮----
"""Test getting npx command on Windows."""
⋮----
# First try fails, second succeeds
⋮----
def test_get_npx_command_not_found(self)
⋮----
"""Test when npx command is not found."""
⋮----
def test_parse_env_var_valid(self)
⋮----
"""Test parsing valid environment variables."""
⋮----
def test_build_uv_command_basic(self)
⋮----
"""Test building basic uv command."""
cmd = cli._build_uv_command("file.py")
⋮----
def test_build_uv_command_with_editable(self)
⋮----
"""Test building uv command with editable flag."""
project_path = Path("/path/to/project")
cmd = cli._build_uv_command("file.py", with_editable=project_path)
⋮----
def test_build_uv_command_with_packages(self)
⋮----
"""Test building uv command with additional packages."""
cmd = cli._build_uv_command("file.py", with_packages=["pkg1", "pkg2"])
⋮----
def test_build_uv_command_full(self)
⋮----
"""Test building full uv command with all options."""
⋮----
cmd = cli._build_uv_command(
⋮----
class TestVersionCommand
⋮----
"""Tests for the version command."""
def test_version_early_exit_with_resilient_parsing(self)
⋮----
"""Test version command exits early with resilient parsing."""
ctx = MagicMock()
⋮----
result = cli.version(ctx)
⋮----
class TestDevCommand
⋮----
"""Tests for the dev command."""
def test_dev_command_success(self, temp_python_file, mock_logger)
⋮----
"""Test successful dev command execution."""
⋮----
mock_server = MagicMock()
⋮----
result = runner.invoke(cli.app, ["dev", str(temp_python_file)])
⋮----
# Check dependencies were passed correctly
⋮----
def test_dev_command_with_ui_port(self, temp_python_file)
⋮----
"""Test dev command with UI port."""
⋮----
result = runner.invoke(
⋮----
# Check environment variables were set
env = mock_run.call_args[1]["env"]
⋮----
def test_dev_command_with_server_port(self, temp_python_file)
⋮----
"""Test dev command with server port."""
⋮----
def test_dev_command_inspector_version(self, temp_python_file)
⋮----
"""Test dev command with specific inspector version."""
⋮----
# Check inspector version was used
inspector_cmd = mock_run.call_args[0][0][1]
⋮----
class TestRunCommand
⋮----
"""Tests for the run command."""
def test_run_command_success(self, temp_python_file)
⋮----
"""Test successful run command execution."""
⋮----
result = runner.invoke(cli.app, ["run", str(temp_python_file)])
⋮----
def test_run_command_with_transport(self, temp_python_file)
⋮----
"""Test run command with transport option."""
⋮----
def test_run_command_with_http_transports(self, temp_python_file)
⋮----
"""Test run command with both http and streamable-http transport options."""
# Test "http" transport
⋮----
# Test "streamable-http" transport (alias for http)
⋮----
def test_run_command_with_host(self, temp_python_file)
⋮----
"""Test run command with host option."""
⋮----
def test_run_command_with_port(self, temp_python_file)
⋮----
"""Test run command with port option."""
⋮----
def test_run_command_with_log_level(self, temp_python_file)
⋮----
"""Test run command with log level option."""
⋮----
def test_run_command_with_multiple_options(self, temp_python_file)
⋮----
"""Test run command with multiple options."""
⋮----
def test_run_command_with_server_args(self, temp_python_file)
⋮----
"""Test run command with server arguments using -- pattern."""

================
File: tests/cli/test_run.py
================
"""Tests for the CLI module."""
⋮----
# Set up test runner
runner = CliRunner()
⋮----
@pytest.fixture
def mock_console()
⋮----
"""Mock the rich console to test output."""
⋮----
@pytest.fixture
def mock_logger()
⋮----
"""Mock the logger to test logging."""
⋮----
@pytest.fixture
def mock_exit()
⋮----
"""Mock sys.exit to prevent tests from exiting."""
⋮----
@pytest.fixture
def temp_python_file(tmp_path)
⋮----
"""Create a temporary Python file with a test server."""
server_code = """
file_path = tmp_path / "test_server.py"
⋮----
@pytest.fixture
def temp_env_file(tmp_path)
⋮----
"""Create a temporary .env file."""
env_content = """
env_path = tmp_path / ".env"
⋮----
class TestHelperFunctions
⋮----
def test_parse_file_path_simple(self)
⋮----
"""Test parsing simple file path."""
⋮----
def test_parse_file_path_with_object(self)
⋮----
"""Test parsing file path with object."""
⋮----
def test_parse_file_path_windows(self)
⋮----
"""Test parsing Windows file path."""
⋮----
def test_parse_file_path_not_file(self, mock_exit)
⋮----
"""Test parsing path that is not a file."""
⋮----
class TestRunCommand
⋮----
"""Tests for the run command."""
def test_run_command_success(self, temp_python_file)
⋮----
"""Test successful run command execution."""
⋮----
mock_server = MagicMock()
⋮----
result = runner.invoke(cli.app, ["run", str(temp_python_file)])
⋮----
def test_run_command_with_transport(self, temp_python_file)
⋮----
"""Test run command with transport option."""
⋮----
result = runner.invoke(
⋮----
def test_run_command_with_host(self, temp_python_file)
⋮----
"""Test run command with host option."""
⋮----
def test_run_command_with_port(self, temp_python_file)
⋮----
"""Test run command with port option."""
⋮----
def test_run_command_with_log_level(self, temp_python_file)
⋮----
"""Test run command with log level option."""
⋮----
def test_run_command_with_multiple_options(self, temp_python_file)
⋮----
"""Test run command with multiple options."""
⋮----
class TestImportServerWithArgs
⋮----
"""Tests for the import_server_with_args function."""
def test_import_server_with_args_no_args(self, temp_python_file)
⋮----
"""Test importing server without arguments."""
⋮----
result = fastmcp.cli.run.import_server_with_args(
⋮----
def test_import_server_with_args_with_args(self, temp_python_file)
⋮----
"""Test importing server with arguments."""
⋮----
original_argv = sys.argv[:]
⋮----
# Verify sys.argv was restored

================
File: tests/client/test_client.py
================
@pytest.fixture
def fastmcp_server()
⋮----
"""Fixture that creates a FastMCP server with tools, resources, and prompts."""
server = FastMCP("TestServer")
# Add a tool
⋮----
@server.tool
    def greet(name: str) -> str
⋮----
"""Greet someone by name."""
⋮----
# Add a second tool
⋮----
@server.tool
    def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.tool
    async def sleep(seconds: float) -> str
⋮----
"""Sleep for a given number of seconds."""
⋮----
# Add a resource
⋮----
@server.resource(uri="data://users")
    async def get_users()
# Add a resource template
⋮----
@server.resource(uri="data://user/{user_id}")
    async def get_user(user_id: str)
# Add a prompt
⋮----
@server.prompt
    def welcome(name: str) -> str
⋮----
"""Example greeting prompt."""
⋮----
@pytest.fixture
def tagged_resources_server()
⋮----
"""Fixture that creates a FastMCP server with tagged resources and templates."""
server = FastMCP("TaggedResourcesServer")
# Add a resource with tags
⋮----
async def get_tagged_data()
# Add a resource template with tags
⋮----
async def get_template_data(id: str)
⋮----
async def test_list_tools(fastmcp_server)
⋮----
"""Test listing tools with InMemoryClient."""
client = Client(transport=FastMCPTransport(fastmcp_server))
⋮----
result = await client.list_tools()
# Check that our tools are available
⋮----
async def test_list_tools_mcp(fastmcp_server)
⋮----
"""Test the list_tools_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.list_tools_mcp()
# Check that we got the raw MCP ListToolsResult object
⋮----
async def test_call_tool(fastmcp_server)
⋮----
"""Test calling a tool with InMemoryClient."""
⋮----
result = await client.call_tool("greet", {"name": "World"})
assert result.content[0].text == "Hello, World!"  # type: ignore[attr-defined]
⋮----
async def test_call_tool_mcp(fastmcp_server)
⋮----
"""Test the call_tool_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.call_tool_mcp("greet", {"name": "World"})
# Check that we got the raw MCP CallToolResult object
⋮----
# The content is a list, so we'll check the first element
# by properly accessing it
content = result.content
⋮----
first_content = content[0]
content_str = str(first_content)
⋮----
async def test_list_resources(fastmcp_server)
⋮----
"""Test listing resources with InMemoryClient."""
⋮----
result = await client.list_resources()
# Check that our resource is available
⋮----
async def test_list_resources_mcp(fastmcp_server)
⋮----
"""Test the list_resources_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.list_resources_mcp()
# Check that we got the raw MCP ListResourcesResult object
⋮----
async def test_list_prompts(fastmcp_server)
⋮----
"""Test listing prompts with InMemoryClient."""
⋮----
result = await client.list_prompts()
# Check that our prompt is available
⋮----
async def test_list_prompts_mcp(fastmcp_server)
⋮----
"""Test the list_prompts_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.list_prompts_mcp()
# Check that we got the raw MCP ListPromptsResult object
⋮----
async def test_get_prompt(fastmcp_server)
⋮----
"""Test getting a prompt with InMemoryClient."""
⋮----
result = await client.get_prompt("welcome", {"name": "Developer"})
# The result should contain our welcome message
assert result.messages[0].content.text == "Welcome to FastMCP, Developer!"  # type: ignore[attr-defined]
⋮----
async def test_get_prompt_mcp(fastmcp_server)
⋮----
"""Test the get_prompt_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.get_prompt_mcp("welcome", {"name": "Developer"})
⋮----
async def test_client_serializes_all_non_string_arguments()
⋮----
"""Test that client always serializes non-string arguments to JSON, regardless of server types."""
⋮----
@server.prompt
    def echo_args(arg1: str, arg2: str, arg3: str) -> str
⋮----
"""Server accepts all string args but client sends mixed types."""
⋮----
client = Client(transport=FastMCPTransport(server))
⋮----
result = await client.get_prompt(
⋮----
"arg1": "hello",  # string - should pass through
"arg2": [1, 2, 3],  # list - should be JSON serialized
"arg3": {"key": "value"},  # dict - should be JSON serialized
⋮----
content = result.messages[0].content.text  # type: ignore[attr-defined]
⋮----
assert "arg2: [1,2,3]" in content  # JSON serialized list
assert 'arg3: {"key":"value"}' in content  # JSON serialized dict
async def test_client_server_type_conversion_integration()
⋮----
"""Test that client serialization works with server-side type conversion."""
⋮----
@server.prompt
    def typed_prompt(numbers: list[int], config: dict[str, str]) -> str
⋮----
"""Server expects typed args - will convert from JSON strings."""
⋮----
async def test_client_serialization_error()
⋮----
"""Test client error when object cannot be serialized."""
⋮----
@server.prompt
    def any_prompt(data: str) -> str
# Create an unserializable object
class UnserializableClass
⋮----
def __init__(self)
⋮----
self.func = lambda x: x  # functions can't be JSON serialized
⋮----
async def test_server_deserialization_error()
⋮----
"""Test server error when JSON string cannot be converted to expected type."""
⋮----
@server.prompt
    def strict_typed_prompt(numbers: list[int]) -> str
⋮----
"""Expects list of integers but will receive invalid JSON."""
⋮----
"numbers": "not valid json"  # This will fail server-side conversion
⋮----
async def test_read_resource_invalid_uri(fastmcp_server)
⋮----
"""Test reading a resource with an invalid URI."""
⋮----
async def test_read_resource(fastmcp_server)
⋮----
"""Test reading a resource with InMemoryClient."""
⋮----
# Use the URI from the resource we know exists in our server
uri = cast(
⋮----
)  # Use cast for type hint only, the URI is valid
result = await client.read_resource(uri)
# The contents should include our user list
contents_str = str(result[0])
⋮----
async def test_read_resource_mcp(fastmcp_server)
⋮----
"""Test the read_resource_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.read_resource_mcp(uri)
# Check that we got the raw MCP ReadResourceResult object
⋮----
contents_str = str(result.contents[0])
⋮----
async def test_client_connection(fastmcp_server)
⋮----
"""Test that connect is idempotent."""
⋮----
# Connect idempotently
⋮----
# Make a request to ensure connection is working
⋮----
async def test_initialize_called_once(fastmcp_server, monkeypatch)
⋮----
mock_initialize = AsyncMock()
⋮----
async def test_initialize_result_connected(fastmcp_server)
⋮----
"""Test that initialize_result returns the correct result when connected."""
⋮----
# Initialize result should not be accessible before connection
⋮----
_ = client.initialize_result
⋮----
# Once connected, initialize_result should be available
result = client.initialize_result
# Verify the initialize result has expected properties
⋮----
async def test_initialize_result_disconnected(fastmcp_server)
⋮----
"""Test that initialize_result raises an error when not connected."""
⋮----
# Connect and then disconnect
⋮----
# After disconnection, initialize_result should raise an error
⋮----
async def test_server_info_custom_version()
⋮----
"""Test that custom version is properly set in serverInfo."""
# Test with custom version
server_with_version = FastMCP("CustomVersionServer", version="1.2.3")
client = Client(transport=FastMCPTransport(server_with_version))
⋮----
# Test without version (backward compatibility)
server_without_version = FastMCP("DefaultVersionServer")
client = Client(transport=FastMCPTransport(server_without_version))
⋮----
# Should fall back to MCP library version
⋮----
)  # Should be different from custom version
async def test_client_nested_context_manager(fastmcp_server)
⋮----
"""Test that the client connects and disconnects once in nested context manager."""
client = Client(fastmcp_server)
# Before connection
⋮----
# During connection
⋮----
session = client._session
# Re-use the same session
⋮----
# After connection
⋮----
async def test_concurrent_client_context_managers()
⋮----
"""
    Test that concurrent client usage doesn't cause cross-task cancel scope issues.
    https://github.com/jlowin/fastmcp/pull/643
    """
# Create a simple server
server = FastMCP("Test Server")
⋮----
@server.tool
    def echo(text: str) -> str
⋮----
"""Echo tool"""
⋮----
# Create client
client = Client(server)
# Track results
results = {}
errors = []
async def use_client(task_id: str, delay: float = 0)
⋮----
"""Use the client with a small delay to ensure overlap"""
⋮----
# Add a small delay to ensure contexts overlap
⋮----
# Make an actual call to exercise the session
tools = await client.list_tools()
⋮----
# Run multiple tasks concurrently
# The key is having them enter and exit the context at different times
⋮----
use_client("task2", 0.01),  # Slight delay to ensure overlap
⋮----
assert all(count == 1 for count in results.values())  # All should see 1 tool
async def test_resource_template(fastmcp_server)
⋮----
"""Test using a resource template with InMemoryClient."""
⋮----
# First, list templates
result = await client.list_resource_templates()
# Check that our template is available
⋮----
# Now use the template with a specific user_id
uri = cast(AnyUrl, "data://user/123")
⋮----
# Check the content matches what we expect for the provided user_id
content_str = str(result[0])
⋮----
async def test_list_resource_templates_mcp(fastmcp_server)
⋮----
"""Test the list_resource_templates_mcp method that returns raw MCP protocol objects."""
⋮----
result = await client.list_resource_templates_mcp()
# Check that we got the raw MCP ListResourceTemplatesResult object
⋮----
async def test_mcp_resource_generation(fastmcp_server)
⋮----
"""Test that resources are properly generated in MCP format."""
⋮----
resources = await client.list_resources()
⋮----
resource = resources[0]
# Verify resource has correct MCP format
⋮----
async def test_mcp_template_generation(fastmcp_server)
⋮----
"""Test that templates are properly generated in MCP format."""
⋮----
templates = await client.list_resource_templates()
⋮----
template = templates[0]
# Verify template has correct MCP format
⋮----
async def test_template_access_via_client(fastmcp_server)
⋮----
"""Test that templates can be accessed through a client."""
⋮----
# Verify template works correctly when accessed
uri = cast(AnyUrl, "data://user/456")
⋮----
async def test_tagged_resource_metadata(tagged_resources_server)
⋮----
"""Test that resource metadata is preserved in MCP format."""
client = Client(transport=FastMCPTransport(tagged_resources_server))
⋮----
# Verify resource metadata is preserved
⋮----
async def test_tagged_template_metadata(tagged_resources_server)
⋮----
"""Test that template metadata is preserved in MCP format."""
⋮----
# Verify template metadata is preserved
⋮----
async def test_tagged_template_functionality(tagged_resources_server)
⋮----
"""Test that tagged templates function correctly when accessed."""
⋮----
# Verify template functionality
uri = cast(AnyUrl, "template://123")
⋮----
class TestErrorHandling
⋮----
async def test_general_tool_exceptions_are_not_masked_by_default(self)
⋮----
mcp = FastMCP("TestServer")
⋮----
@mcp.tool
        def error_tool()
client = Client(transport=FastMCPTransport(mcp))
⋮----
result = await client.call_tool_mcp("error_tool", {})
⋮----
assert "test error" in result.content[0].text  # type: ignore[attr-defined]
assert "abc" in result.content[0].text  # type: ignore[attr-defined]
async def test_general_tool_exceptions_are_masked_when_enabled(self)
⋮----
mcp = FastMCP("TestServer", mask_error_details=True)
⋮----
assert "test error" not in result.content[0].text  # type: ignore[attr-defined]
assert "abc" not in result.content[0].text  # type: ignore[attr-defined]
async def test_specific_tool_errors_are_sent_to_client(self)
⋮----
@mcp.tool
        def custom_error_tool()
⋮----
result = await client.call_tool_mcp("custom_error_tool", {})
⋮----
async def test_general_resource_exceptions_are_not_masked_by_default(self)
⋮----
@mcp.resource(uri="exception://resource")
        async def exception_resource()
⋮----
async def test_general_resource_exceptions_are_masked_when_enabled(self)
async def test_resource_errors_are_sent_to_client(self)
⋮----
@mcp.resource(uri="error://resource")
        async def error_resource()
⋮----
async def test_general_template_exceptions_are_not_masked_by_default(self)
⋮----
@mcp.resource(uri="exception://resource/{id}")
        async def exception_resource(id: str)
⋮----
async def test_general_template_exceptions_are_masked_when_enabled(self)
async def test_template_errors_are_sent_to_client(self)
⋮----
@mcp.resource(uri="error://resource/{id}")
        async def error_resource(id: str)
⋮----
class TestTimeout
⋮----
async def test_timeout(self, fastmcp_server: FastMCP)
async def test_timeout_tool_call(self, fastmcp_server: FastMCP)
⋮----
class TestInferTransport
⋮----
"""Tests for the infer_transport function."""
⋮----
def test_url_returns_sse_transport(self, url)
⋮----
"""Test that URLs with /sse/ pattern return SSETransport."""
⋮----
def test_url_returns_streamable_http_transport(self, url)
⋮----
"""Test that URLs without /sse/ pattern return StreamableHttpTransport."""
⋮----
def test_infer_remote_transport_from_config(self)
⋮----
config = {
transport = infer_transport(config)
⋮----
def test_infer_local_transport_from_config(self)
def test_config_with_no_servers(self)
⋮----
"""Test that an empty MCPConfig raises a ValueError."""
config = {"mcpServers": {}}
⋮----
def test_mcpconfigtransport_with_no_servers(self)
⋮----
"""Test that MCPConfigTransport raises a ValueError when initialized with an empty config."""
⋮----
def test_infer_composite_client(self)
def test_infer_fastmcp_server(self, fastmcp_server)
⋮----
"""FastMCP server instances should infer to FastMCPTransport."""
transport = infer_transport(fastmcp_server)
⋮----
def test_infer_fastmcp_v1_server(self)
⋮----
"""FastMCP 1.0 server instances should infer to FastMCPTransport."""
⋮----
server = FastMCP1()
transport = infer_transport(server)
⋮----
class TestAuth
⋮----
def test_default_auth_is_none(self)
⋮----
client = Client(transport=StreamableHttpTransport("http://localhost:8000"))
⋮----
def test_stdio_doesnt_support_auth(self)
def test_oauth_literal_sets_up_oauth_shttp(self)
⋮----
client = Client(
⋮----
def test_oauth_literal_pass_direct_to_transport(self)
def test_oauth_literal_sets_up_oauth_sse(self)
⋮----
client = Client(transport=SSETransport("http://localhost:8000"), auth="oauth")
⋮----
def test_oauth_literal_pass_direct_to_transport_sse(self)
⋮----
client = Client(transport=SSETransport("http://localhost:8000", auth="oauth"))
⋮----
def test_auth_string_sets_up_bearer_auth_shttp(self)
def test_auth_string_pass_direct_to_transport_shttp(self)
def test_auth_string_sets_up_bearer_auth_sse(self)
def test_auth_string_pass_direct_to_transport_sse(self)

================
File: tests/client/test_elicitation.py
================
@pytest.fixture
def fastmcp_server()
⋮----
mcp = FastMCP("TestServer")
⋮----
@dataclass
    class Person
⋮----
name: str
⋮----
@mcp.tool
    async def ask_for_name(context: Context) -> str
⋮----
result = await context.elicit(
⋮----
@mcp.tool
    def simple_test() -> str
⋮----
async def test_elicitation_with_no_handler(fastmcp_server)
⋮----
"""Test that elicitation works without a handler."""
⋮----
async def test_elicitation_accept_content(fastmcp_server)
⋮----
"""Test basic elicitation functionality."""
async def elicitation_handler(message, response_type, params, ctx)
⋮----
# Mock user providing their name
⋮----
result = await client.call_tool("ask_for_name", {})
⋮----
async def test_elicitation_decline(fastmcp_server)
⋮----
"""Test that elicitation handler receives correct parameters."""
⋮----
async def test_default_response_type(fastmcp_server)
⋮----
"""Test elicitation with string content."""
⋮----
@mcp.tool
    async def ask_for_color(context: Context) -> str
⋮----
# Default schema should be string
⋮----
# Mock user providing their favorite color as string in content dict
⋮----
result = await client.call_tool("ask_for_color", {})
⋮----
async def test_elicitation_handler_parameters()
⋮----
captured_params = {}
⋮----
@mcp.tool
    async def test_tool(context: Context) -> str
⋮----
async def test_elicitation_cancel_action()
⋮----
"""Test user canceling elicitation request."""
⋮----
@mcp.tool
    async def ask_for_optional_info(context: Context) -> str
⋮----
result = await client.call_tool("ask_for_optional_info", {})
⋮----
class TestScalarResponseTypes
⋮----
async def test_elicitation_str_response(self)
⋮----
"""Test elicitation with string schema."""
⋮----
@mcp.tool
        async def my_tool(context: Context) -> str
⋮----
result = await context.elicit(message="", response_type=str)
return result.data  # type: ignore[attr-defined]
async def elicitation_handler(message, response_type, params, ctx)
⋮----
result = await client.call_tool("my_tool", {})
⋮----
async def test_elicitation_int_response(self)
⋮----
"""Test elicitation with number schema."""
⋮----
@mcp.tool
        async def my_tool(context: Context) -> int
⋮----
result = await context.elicit(message="", response_type=int)
⋮----
async def test_elicitation_float_response(self)
⋮----
@mcp.tool
        async def my_tool(context: Context) -> float
⋮----
result = await context.elicit(message="", response_type=float)
⋮----
async def test_elicitation_bool_response(self)
⋮----
"""Test elicitation with boolean schema."""
⋮----
@mcp.tool
        async def my_tool(context: Context) -> bool
⋮----
result = await context.elicit(message="", response_type=bool)
⋮----
async def test_elicitation_literal_response(self)
⋮----
"""Test elicitation with literal schema."""
⋮----
@mcp.tool
        async def my_tool(context: Context) -> Literal["x", "y"]
⋮----
result = await context.elicit(message="", response_type=Literal["x", "y"])  # type: ignore
⋮----
async def test_elicitation_enum_response(self)
⋮----
"""Test elicitation with enum schema."""
⋮----
class ResponseEnum(Enum)
⋮----
X = "x"
Y = "y"
⋮----
@mcp.tool
        async def my_tool(context: Context) -> ResponseEnum
⋮----
result = await context.elicit(message="", response_type=ResponseEnum)
⋮----
async def test_elicitation_list_response(self)
⋮----
"""Test elicitation with list schema."""
⋮----
result = await context.elicit(message="", response_type=["x", "y"])
⋮----
async def test_elicitation_handler_error()
⋮----
"""Test error handling in elicitation handler."""
⋮----
@mcp.tool
    async def failing_elicit(context: Context) -> str
⋮----
result = await context.elicit(message="This will fail", response_type=str)
⋮----
result = await client.call_tool("failing_elicit", {})
⋮----
async def test_elicitation_multiple_calls()
⋮----
"""Test multiple elicitation calls in sequence."""
⋮----
@mcp.tool
    async def multi_step_form(context: Context) -> str
⋮----
# First question
name_result = await context.elicit(
⋮----
# Second question
age_result = await context.elicit(message="What's your age?", response_type=int)
⋮----
call_count = 0
⋮----
result = await client.call_tool("multi_step_form", {})
⋮----
@dataclass
class UserInfo
⋮----
age: int
class UserInfoTypedDict(TypedDict)
class UserInfoPydantic(BaseModel)
⋮----
"""Test elicitation with dataclass response type."""
⋮----
@mcp.tool
    async def get_user_info(context: Context) -> str
⋮----
# Verify we get the dataclass type
⋮----
# Verify the schema has the dataclass fields (available in params)
schema = params.requestedSchema
⋮----
result = await client.call_tool("get_user_info", {})
⋮----
async def test_all_primitive_field_types()
⋮----
class DataEnum(Enum)
⋮----
@dataclass
    class Data
⋮----
integer: int
float_: float
number: int | float
boolean: bool
string: str
constant: Literal["x"]
union: Literal["x"] | Literal["y"]
choice: Literal["x", "y"]
enum: DataEnum
⋮----
@mcp.tool
    async def get_data(context: Context) -> Data
⋮----
result = await context.elicit(message="Enter data", response_type=Data)
return result.data  # type: ignore[attr-defined]
⋮----
result = await client.call_tool("get_data", {})
# Now all literal/enum fields should be preserved as strings
result_data = asdict(result.data)
result_data_enum = result_data.pop("enum")
assert result_data_enum == "x"  # Should be a string now, not an enum
⋮----
class TestValidation
⋮----
async def test_schema_validation_rejects_non_object(self)
⋮----
"""Test that non-object schemas are rejected."""
⋮----
async def test_schema_validation_rejects_empty_object(self)
⋮----
"""Test that object schemas without properties are rejected."""
⋮----
async def test_schema_validation_rejects_nested_objects(self)
⋮----
"""Test that nested object schemas are rejected."""
⋮----
async def test_schema_validation_rejects_arrays(self)
⋮----
"""Test that array schemas are rejected."""
⋮----
class TestPatternMatching
⋮----
async def test_pattern_matching_accept(self)
⋮----
"""Test pattern matching with AcceptedElicitation."""
⋮----
@mcp.tool
        async def pattern_match_tool(context: Context) -> str
⋮----
result = await context.elicit("Enter your name:", response_type=str)
⋮----
result = await client.call_tool("pattern_match_tool", {})
⋮----
async def test_pattern_matching_decline(self)
⋮----
"""Test pattern matching with DeclinedElicitation."""
⋮----
async def test_pattern_matching_cancel(self)
⋮----
"""Test pattern matching with CancelledElicitation."""

================
File: tests/client/test_logs.py
================
class LogHandler
⋮----
def __init__(self)
async def handle_log(self, message: LogMessage) -> None
⋮----
@pytest.fixture
def fastmcp_server()
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    async def log(context: Context) -> None
⋮----
class TestClientLogs
⋮----
async def test_log(self, fastmcp_server: FastMCP)
⋮----
log_handler = LogHandler()
⋮----
async def test_echo_log(self, fastmcp_server: FastMCP)

================
File: tests/client/test_notifications.py
================
@dataclass
class NotificationRecording
⋮----
"""Record of a notification that was received."""
method: str
notification: mcp.types.ServerNotification
class RecordingMessageHandler(MessageHandler)
⋮----
"""A message handler that records all notifications."""
def __init__(self, name: str | None = None)
async def on_notification(self, message: mcp.types.ServerNotification) -> None
⋮----
"""Record all notifications."""
⋮----
"""Get all recorded notifications, optionally filtered by method."""
⋮----
def assert_notification_sent(self, method: str, times: int = 1) -> bool
⋮----
"""Assert that a notification was sent a specific number of times."""
notifications = self.get_notifications(method)
actual_times = len(notifications)
⋮----
def assert_notification_not_sent(self, method: str) -> bool
⋮----
"""Assert that a notification was not sent."""
⋮----
def reset(self)
⋮----
"""Clear all recorded notifications."""
⋮----
@pytest.fixture
def recording_message_handler()
⋮----
"""Fixture that provides a recording message handler instance."""
handler = RecordingMessageHandler(name="recording_message_handler")
⋮----
@pytest.fixture
def notification_test_server(recording_message_handler)
⋮----
"""Create a server for testing notifications."""
mcp = FastMCP(name="NotificationTestServer")
# Create a target tool that can be enabled/disabled
def target_tool() -> str
⋮----
"""A tool that can be enabled/disabled."""
⋮----
target_tool_obj = Tool.from_function(target_tool)
⋮----
# Tool to enable the target tool
⋮----
@mcp.tool
    async def enable_target_tool(ctx: Context) -> str
⋮----
"""Enable the target tool."""
# Find and enable the target tool
⋮----
tool = await ctx.fastmcp.get_tool("target_tool")
⋮----
# Tool to disable the target tool
⋮----
@mcp.tool
    async def disable_target_tool(ctx: Context) -> str
⋮----
"""Disable the target tool."""
# Find and disable the target tool
⋮----
class TestToolNotifications
⋮----
"""Test tool list changed notifications."""
⋮----
"""Test that enabling a tool sends a tool list changed notification."""
⋮----
# Reset any initialization notifications
⋮----
# Enable the target tool
result = await client.call_tool("enable_target_tool", {})
⋮----
# Check that notification was sent
⋮----
"""Test that disabling a tool sends a tool list changed notification."""
⋮----
# Disable the target tool
result = await client.call_tool("disable_target_tool", {})
⋮----
"""Test that multiple rapid tool changes result in a single notification."""
⋮----
# Enable and disable multiple times in the same context
# This should result in deduplication
⋮----
# Should have 3 notifications (one per tool call context)
⋮----
@pytest.fixture
def resource_notification_test_server(recording_message_handler)
⋮----
"""Create a server for testing resource notifications."""
mcp = FastMCP(name="ResourceNotificationTestServer")
# Create a target resource that can be enabled/disabled
⋮----
@mcp.resource("resource://target")
    def target_resource() -> str
⋮----
"""A resource that can be enabled/disabled."""
⋮----
# Tool to enable the target resource
⋮----
@mcp.tool
    async def enable_target_resource(ctx: Context) -> str
⋮----
"""Enable the target resource."""
⋮----
resource = await ctx.fastmcp.get_resource("resource://target")
⋮----
# Tool to disable the target resource
⋮----
@mcp.tool
    async def disable_target_resource(ctx: Context) -> str
⋮----
"""Disable the target resource."""
⋮----
class TestResourceNotifications
⋮----
"""Test resource list changed notifications."""
⋮----
"""Test that enabling a resource sends a resource list changed notification."""
⋮----
# Enable the target resource
result = await client.call_tool("enable_target_resource", {})
⋮----
"""Test that disabling a resource sends a resource list changed notification."""
⋮----
# Disable the target resource
result = await client.call_tool("disable_target_resource", {})
⋮----
@pytest.fixture
def prompt_notification_test_server(recording_message_handler)
⋮----
"""Create a server for testing prompt notifications."""
mcp = FastMCP(name="PromptNotificationTestServer")
# Create a target prompt that can be enabled/disabled
⋮----
@mcp.prompt
    def target_prompt() -> str
⋮----
"""A prompt that can be enabled/disabled."""
⋮----
# Tool to enable the target prompt
⋮----
@mcp.tool
    async def enable_target_prompt(ctx: Context) -> str
⋮----
"""Enable the target prompt."""
⋮----
prompt = await ctx.fastmcp.get_prompt("target_prompt")
⋮----
# Tool to disable the target prompt
⋮----
@mcp.tool
    async def disable_target_prompt(ctx: Context) -> str
⋮----
"""Disable the target prompt."""
⋮----
class TestPromptNotifications
⋮----
"""Test prompt list changed notifications."""
⋮----
"""Test that enabling a prompt sends a prompt list changed notification."""
⋮----
# Enable the target prompt
result = await client.call_tool("enable_target_prompt", {})
⋮----
"""Test that disabling a prompt sends a prompt list changed notification."""
⋮----
# Disable the target prompt
result = await client.call_tool("disable_target_prompt", {})
⋮----
class TestMessageHandlerGeneral
⋮----
"""Test the message handler functionality in general."""
⋮----
"""Test that the message handler receives all types of notifications."""
⋮----
# Trigger a tool notification
⋮----
# Verify the handler received the notification
all_notifications = recording_message_handler.get_notifications()
⋮----
"""Test that notification filtering works correctly."""
⋮----
# Trigger tool notifications
⋮----
# Test filtering
tool_notifications = recording_message_handler.get_notifications(
⋮----
# Test non-existent filter
resource_notifications = recording_message_handler.get_notifications(
⋮----
"""Test that notifications have the correct structure."""
⋮----
# Trigger a notification
⋮----
# Check notification structure
notifications = recording_message_handler.get_notifications(
⋮----
notification = notifications[0]

================
File: tests/client/test_openapi.py
================
def fastmcp_server_for_headers() -> FastMCP
⋮----
app = FastAPI()
⋮----
@app.get("/headers")
    def get_headers(request: Request)
⋮----
@app.get("/headers/{header_name}")
    def get_header_by_name(header_name: str, request: Request)
⋮----
@app.post("/headers")
    def post_headers(request: Request)
mcp = FastMCP.from_fastapi(
⋮----
# GET requests with path parameters go to ResourceTemplate
⋮----
# GET requests without path parameters go to Resource
⋮----
def run_server(host: str, port: int, **kwargs) -> None
def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None
⋮----
client = Client(transport=StreamableHttpTransport(shttp_url))
app = FastMCP.as_proxy(client)
⋮----
class TestClientHeaders
⋮----
@pytest.fixture(scope="class")
    def shttp_server(self) -> Generator[str, None, None]
⋮----
@pytest.fixture(scope="class")
    def sse_server(self) -> Generator[str, None, None]
⋮----
@pytest.fixture(scope="class")
    def proxy_server(self, shttp_server: str) -> Generator[str, None, None]
async def test_client_headers_sse_resource(self, sse_server: str)
⋮----
result = await client.read_resource("resource://get_headers_headers_get")
headers = json.loads(result[0].text)  # type: ignore[attr-defined]
⋮----
async def test_client_headers_shttp_resource(self, shttp_server: str)
async def test_client_headers_sse_resource_template(self, sse_server: str)
⋮----
result = await client.read_resource(
header = json.loads(result[0].text)  # type: ignore[attr-defined]
⋮----
async def test_client_headers_shttp_resource_template(self, shttp_server: str)
async def test_client_headers_sse_tool(self, sse_server: str)
⋮----
result = await client.call_tool("post_headers_headers_post")
headers: dict[str, str] = result.data
⋮----
async def test_client_headers_shttp_tool(self, shttp_server: str)
async def test_client_overrides_server_headers(self, shttp_server: str)
async def test_client_with_excluded_header_is_ignored(self, sse_server: str)
async def test_client_headers_proxy(self, proxy_server: str)
⋮----
"""
        Test that client headers are passed through the proxy to the remove server.
        """

================
File: tests/client/test_progress.py
================
PROGRESS_MESSAGES = []
⋮----
@pytest.fixture(autouse=True)
def clear_progress_messages()
⋮----
@pytest.fixture
def fastmcp_server()
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    async def progress_tool(context: Context) -> int
⋮----
EXPECTED_PROGRESS_MESSAGES = [
⋮----
async def test_progress_handler(fastmcp_server: FastMCP)
async def test_progress_handler_can_be_supplied_on_tool_call(fastmcp_server: FastMCP)

================
File: tests/client/test_roots.py
================
@pytest.fixture
def fastmcp_server()
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    async def list_roots(context: Context) -> list[str]
⋮----
roots = await context.list_roots()
⋮----
class TestClientRoots
⋮----
@pytest.mark.parametrize("roots", [["x"], ["x", "y"]])
    async def test_invalid_roots(self, fastmcp_server: FastMCP, roots: list[str])
⋮----
"""
        Roots must be URIs
        """
⋮----
@pytest.mark.parametrize("roots", [["https://x.com"]])
    async def test_invalid_urls(self, fastmcp_server: FastMCP, roots: list[str])
⋮----
"""
        At this time, root URIs must start with file://
        """
⋮----
@pytest.mark.parametrize("roots", [["file://x/y/z", "file://x/y/z"]])
    async def test_valid_roots(self, fastmcp_server: FastMCP, roots: list[str])
⋮----
result = await client.call_tool("list_roots", {})

================
File: tests/client/test_sampling.py
================
@pytest.fixture
def fastmcp_server()
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    async def simple_sample(message: str, context: Context) -> str
⋮----
result = await context.sample("Hello, world!")
⋮----
@mcp.tool
    async def sample_with_system_prompt(message: str, context: Context) -> str
⋮----
result = await context.sample("Hello, world!", system_prompt="You love FastMCP")
⋮----
@mcp.tool
    async def sample_with_messages(message: str, context: Context) -> str
⋮----
result = await context.sample(
⋮----
async def test_simple_sampling(fastmcp_server: FastMCP)
⋮----
result = await client.call_tool("simple_sample", {"message": "Hello, world!"})
⋮----
async def test_sampling_with_system_prompt(fastmcp_server: FastMCP)
⋮----
result = await client.call_tool(
⋮----
async def test_sampling_with_messages(fastmcp_server: FastMCP)

================
File: tests/client/test_sse.py
================
def fastmcp_server()
⋮----
"""Fixture that creates a FastMCP server with tools, resources, and prompts."""
server = FastMCP("TestServer")
# Add a tool
⋮----
@server.tool
    def greet(name: str) -> str
⋮----
"""Greet someone by name."""
⋮----
# Add a second tool
⋮----
@server.tool
    def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.tool
    async def sleep(seconds: float) -> str
⋮----
"""Sleep for a given number of seconds."""
⋮----
# Add a resource
⋮----
@server.resource(uri="data://users")
    async def get_users()
# Add a resource template
⋮----
@server.resource(uri="data://user/{user_id}")
    async def get_user(user_id: str)
⋮----
@server.resource(uri="request://headers")
    async def get_headers() -> dict[str, str]
⋮----
request = get_http_request()
⋮----
# Add a prompt
⋮----
@server.prompt
    def welcome(name: str) -> str
⋮----
"""Example greeting prompt."""
⋮----
def run_server(host: str, port: int, **kwargs) -> None
⋮----
@pytest.fixture(autouse=True, scope="module")
def sse_server() -> Generator[str, None, None]
async def test_ping(sse_server: str)
⋮----
"""Test pinging the server."""
⋮----
result = await client.ping()
⋮----
async def test_http_headers(sse_server: str)
⋮----
"""Test getting HTTP headers from the server."""
⋮----
raw_result = await client.read_resource("request://headers")
json_result = json.loads(raw_result[0].text)  # type: ignore[attr-defined]
⋮----
def run_nested_server(host: str, port: int) -> None
⋮----
app = fastmcp_server().sse_app(path="/mcp/sse/", message_path="/mcp/messages")
mount = Starlette(routes=[Mount("/nest-inner", app=app)])
mount2 = Starlette(routes=[Mount("/nest-outer", app=mount)])
server = uvicorn.Server(
⋮----
async def test_run_server_on_path()
async def test_nested_sse_server_resolves_correctly()
⋮----
# tests patch for
# https://github.com/modelcontextprotocol/python-sdk/pull/659
⋮----
class TestTimeout
⋮----
async def test_timeout(self, sse_server: str)
async def test_timeout_tool_call(self, sse_server: str)
⋮----
"""
        With SSE, the tool call timeout always takes precedence over the client.
        Note: on Windows, the behavior appears unpredictable.
        """

================
File: tests/client/test_stdio.py
================
class TestKeepAlive
⋮----
# https://github.com/jlowin/fastmcp/issues/581
⋮----
@pytest.fixture
    def stdio_script(self, tmp_path)
⋮----
script = inspect.cleandoc('''
script_file = tmp_path / "stdio.py"
⋮----
async def test_keep_alive_default_true(self)
⋮----
client = Client(transport=StdioTransport(command="python", args=[""]))
⋮----
async def test_keep_alive_set_false(self)
⋮----
client = Client(
⋮----
client = Client(transport=PythonStdioTransport(script_path=stdio_script))
⋮----
result1 = await client.call_tool("pid")
pid1: int = result1.data
⋮----
result2 = await client.call_tool("pid")
pid2: int = result2.data
⋮----
async def test_keep_alive_starts_new_session_if_manually_closed(self, stdio_script)
async def test_keep_alive_maintains_session_if_reentered(self, stdio_script)
⋮----
result3 = await client.call_tool("pid")
pid3: int = result3.data
⋮----
async def test_close_session_and_try_to_use_client_raises_error(self, stdio_script)
async def test_session_task_failure_raises_immediately_on_enter(self)
⋮----
# Use a command that will fail to start
⋮----
# Should raise RuntimeError immediately, not defer until first use

================
File: tests/client/test_streamable_http.py
================
def fastmcp_server()
⋮----
"""Fixture that creates a FastMCP server with tools, resources, and prompts."""
server = FastMCP("TestServer")
# Add a tool
⋮----
@server.tool
    def greet(name: str) -> str
⋮----
"""Greet someone by name."""
⋮----
@server.tool
    async def elicit(ctx: Context) -> str
⋮----
"""Elicit a response from the user."""
result = await ctx.elicit("What is your name?", response_type=str)
⋮----
# Add a second tool
⋮----
@server.tool
    def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.tool
    async def sleep(seconds: float) -> str
⋮----
"""Sleep for a given number of seconds."""
⋮----
@server.tool
    async def greet_with_progress(name: str, ctx: Context) -> str
⋮----
"""Report progress for a greeting."""
⋮----
# Add a resource
⋮----
@server.resource(uri="data://users")
    async def get_users()
# Add a resource template
⋮----
@server.resource(uri="data://user/{user_id}")
    async def get_user(user_id: str)
⋮----
@server.resource(uri="request://headers")
    async def get_headers() -> dict[str, str]
⋮----
request = get_http_request()
⋮----
# Add a prompt
⋮----
@server.prompt
    def welcome(name: str) -> str
⋮----
"""Example greeting prompt."""
⋮----
def run_server(host: str, port: int, stateless_http: bool = False, **kwargs) -> None
⋮----
server = fastmcp_server()
⋮----
def run_nested_server(host: str, port: int) -> None
⋮----
mcp_app = fastmcp_server().http_app(path="/final/mcp/")
mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)])
mount2 = Starlette(
server = uvicorn.Server(
⋮----
"""Test that the "streamable-http" transport alias works."""
⋮----
async def test_ping(streamable_http_server: str)
⋮----
"""Test pinging the server."""
⋮----
result = await client.ping()
⋮----
async def test_http_headers(streamable_http_server: str)
⋮----
"""Test getting HTTP headers from the server."""
⋮----
raw_result = await client.read_resource("request://headers")
json_result = json.loads(raw_result[0].text)  # type: ignore[attr-defined]
⋮----
@pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True)
async def test_greet_with_progress_tool(streamable_http_server: str)
⋮----
"""Test calling the greet tool."""
progress_handler = AsyncMock(return_value=None)
⋮----
result = await client.call_tool("greet_with_progress", {"name": "Alice"})
⋮----
@pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True)
async def test_elicitation_tool(streamable_http_server: str)
⋮----
"""Test calling the elicitation tool in both stateless and stateful modes."""
async def elicitation_handler(message, response_type, params, ctx)
⋮----
result = await client.call_tool("elicit")
⋮----
async def test_nested_streamable_http_server_resolves_correctly()
⋮----
# tests patch for
# https://github.com/modelcontextprotocol/python-sdk/pull/659
⋮----
class TestTimeout
⋮----
async def test_timeout(self, streamable_http_server: str)
⋮----
# note this transport behaves differently than others and raises
# McpError from the *client* context
⋮----
async def test_timeout_tool_call(self, streamable_http_server: str)

================
File: tests/contrib/__init__.py
================
# This file makes Python treat the directory as a package.

================
File: tests/contrib/test_bulk_tool_caller.py
================
class ToolException(Exception)
⋮----
"""Custom exception for tool errors."""
⋮----
async def error_tool(arg1: str) -> dict[str, Any]
⋮----
"""A tool that raises an error for testing purposes."""
⋮----
def error_tool_result_factory(arg1: str) -> CallToolRequestResult
⋮----
"""Generates the expected error result for error_tool."""
# Mimic the error message format generated by BulkToolCaller when catching ToolException
formatted_error_text = (
⋮----
async def echo_tool(arg1: str) -> str
⋮----
"""A simple tool that echoes arguments or raises an error."""
⋮----
def echo_tool_result_factory(arg1: str) -> CallToolRequestResult
⋮----
"""A tool that returns a result based on the input arguments."""
⋮----
async def no_return_tool(arg1: str) -> None
def no_return_tool_result_factory(arg1: str) -> CallToolRequestResult
⋮----
@pytest.fixture(scope="module")
def live_server_with_tool() -> FastMCP
⋮----
"""Fixture to create a FastMCP server instance with the echo_tool registered."""
server = FastMCP()
⋮----
@pytest.fixture
def bulk_caller_live(live_server_with_tool: FastMCP) -> BulkToolCaller
⋮----
"""Fixture to create a BulkToolCaller instance connected to the live server."""
bulk_tool_caller = BulkToolCaller()
⋮----
ECHO_TOOL_NAME = "echo_tool"
ERROR_TOOL_NAME = "error_tool"
NO_RETURN_TOOL_NAME = "no_return_tool"
async def test_call_tool_bulk_single_success(bulk_caller_live: BulkToolCaller)
⋮----
"""Test single successful call via call_tool_bulk using echo_tool."""
tool_arguments = [{"arg1": "value1"}]
expected_result = echo_tool_result_factory(**tool_arguments[0])
results = await bulk_caller_live.call_tool_bulk(ECHO_TOOL_NAME, tool_arguments)
⋮----
result = results[0]
⋮----
async def test_call_tool_bulk_multiple_success(bulk_caller_live: BulkToolCaller)
⋮----
"""Test multiple successful calls via call_tool_bulk using echo_tool."""
tool_arguments = [{"arg1": "value1"}, {"arg1": "value2"}]
expected_results = [echo_tool_result_factory(**args) for args in tool_arguments]
⋮----
async def test_call_tool_bulk_error_stops(bulk_caller_live: BulkToolCaller)
⋮----
"""Test call_tool_bulk stops on first error using error_tool."""
tool_arguments = [{"arg1": "error_value"}, {"arg1": "value2"}]
expected_result = error_tool_result_factory(**tool_arguments[0])
results = await bulk_caller_live.call_tool_bulk(
⋮----
async def test_call_tool_bulk_error_continues(bulk_caller_live: BulkToolCaller)
⋮----
"""Test call_tool_bulk continues on error using error_tool and echo_tool."""
tool_arguments = [{"arg1": "error_value"}, {"arg1": "success_value"}]
expected_error_result = error_tool_result_factory(**tool_arguments[0])
expected_success_result = echo_tool_result_factory(**tool_arguments[1])
tool_calls = [
results = await bulk_caller_live.call_tools_bulk(tool_calls, continue_on_error=True)
⋮----
error_result = results[0]
⋮----
success_result = results[1]
⋮----
async def test_call_tools_bulk_single_success(bulk_caller_live: BulkToolCaller)
⋮----
"""Test single successful call via call_tools_bulk using echo_tool."""
tool_calls = [CallToolRequest(tool=ECHO_TOOL_NAME, arguments={"arg1": "value1"})]
expected_result = echo_tool_result_factory(**tool_calls[0].arguments)
results = await bulk_caller_live.call_tools_bulk(tool_calls)
⋮----
async def test_call_tools_bulk_multiple_success(bulk_caller_live: BulkToolCaller)
⋮----
"""Test multiple successful calls via call_tools_bulk with different tools."""
⋮----
expected_results = [
⋮----
async def test_call_tools_bulk_error_stops(bulk_caller_live: BulkToolCaller)
⋮----
"""Test call_tools_bulk stops on first error using error_tool."""
⋮----
expected_result = error_tool_result_factory(**tool_calls[0].arguments)
results = await bulk_caller_live.call_tools_bulk(
⋮----
async def test_call_tools_bulk_error_continues(bulk_caller_live: BulkToolCaller)
⋮----
"""Test call_tools_bulk continues on error using error_tool and echo_tool."""
⋮----
expected_error_result = error_tool_result_factory(**tool_calls[0].arguments)
expected_success_result = echo_tool_result_factory(**tool_calls[1].arguments)

================
File: tests/contrib/test_component_manager.py
================
class TestComponentManagementRoutes
⋮----
"""Test the component management routes for tools, resources, and prompts."""
⋮----
@pytest.fixture
    def mounted_mcp(self)
⋮----
"""Create a FastMCP server with a mounted sub-server and a tool, resource, and prompt on the sub-server."""
mounted_mcp = FastMCP("SubServer")
⋮----
@mounted_mcp.tool()
        def mounted_tool() -> str
⋮----
"""Test tool for tool management routes."""
⋮----
@mounted_mcp.resource("data://mounted_resource")
        def mounted_resource() -> str
⋮----
"""Test resource for tool management routes."""
⋮----
# Add a test resource
⋮----
@mounted_mcp.resource("data://mounted_resource/{id}")
        def test_template(id: str) -> dict
⋮----
"""Test template for tool management routes."""
⋮----
@mounted_mcp.prompt()
        def mounted_prompt() -> str
⋮----
"""Test prompt for tool management routes."""
⋮----
@pytest.fixture
    def mcp(self, mounted_mcp)
⋮----
"""Create a FastMCP server with test tools, resources, and prompts."""
mcp = FastMCP("TestServer")
⋮----
# Add a test tool
⋮----
@mcp.tool
        def test_tool() -> str
⋮----
@mcp.resource("data://test_resource")
        def test_resource() -> str
⋮----
@mcp.resource("data://test_resource/{id}")
        def test_template(id: str) -> dict
# Add a test prompt
⋮----
@mcp.prompt
        def test_prompt() -> str
⋮----
@pytest.fixture
    def client(self, mcp)
⋮----
"""Create a test client for the FastMCP server."""
⋮----
async def test_enable_tool_route(self, client, mcp)
⋮----
"""Test enabling a tool via the HTTP route."""
# First disable the tool
tool = await mcp._tool_manager.get_tool("test_tool")
⋮----
# Enable the tool via the HTTP route
response = client.post("/tools/test_tool/enable")
⋮----
# Verify the tool is enabled
⋮----
async def test_disable_tool_route(self, client, mcp)
⋮----
"""Test disabling a tool via the HTTP route."""
# First ensure the tool is enabled
⋮----
# Disable the tool via the HTTP route
response = client.post("/tools/test_tool/disable")
⋮----
# Verify the tool is disabled
⋮----
async def test_enable_resource_route(self, client, mcp)
⋮----
"""Test enabling a resource via the HTTP route."""
# First disable the resource
resource = await mcp._resource_manager.get_resource("data://test_resource")
⋮----
# Enable the resource via the HTTP route
response = client.post("/resources/data://test_resource/enable")
⋮----
# Verify the resource is enabled
⋮----
async def test_disable_resource_route(self, client, mcp)
⋮----
"""Test disabling a resource via the HTTP route."""
# First ensure the resource is enabled
⋮----
# Disable the resource via the HTTP route
response = client.post("/resources/data://test_resource/disable")
⋮----
# Verify the resource is disabled
⋮----
async def test_enable_template_route(self, client, mcp)
⋮----
"""Test enabling a resource on a mounted server via the parent server's HTTP route."""
key = "data://test_resource/{id}"
resource = mcp._resource_manager._templates[key]
⋮----
response = client.post("/resources/data://test_resource/{id}/enable")
⋮----
async def test_disable_template_route(self, client, mcp)
⋮----
"""Test disabling a resource on a mounted server via the parent server's HTTP route."""
⋮----
response = client.post("/resources/data://test_resource/{id}/disable")
⋮----
async def test_enable_prompt_route(self, client, mcp)
⋮----
"""Test enabling a prompt via the HTTP route."""
# First disable the prompt
prompt = await mcp._prompt_manager.get_prompt("test_prompt")
⋮----
# Enable the prompt via the HTTP route
response = client.post("/prompts/test_prompt/enable")
⋮----
# Verify the prompt is enabled
⋮----
async def test_disable_prompt_route(self, client, mcp)
⋮----
"""Test disabling a prompt via the HTTP route."""
# First ensure the prompt is enabled
⋮----
# Disable the prompt via the HTTP route
response = client.post("/prompts/test_prompt/disable")
⋮----
# Verify the prompt is disabled
⋮----
async def test_enable_tool_route_on_mounted_server(self, client, mounted_mcp)
⋮----
"""Test enabling a tool on a mounted server via the parent server's HTTP route."""
# Disable the tool on the sub-server
sub_tool = await mounted_mcp._tool_manager.get_tool("mounted_tool")
⋮----
# Enable via parent
response = client.post("/tools/sub_mounted_tool/enable")
⋮----
# Confirm disabled on sub-server
⋮----
async def test_disable_tool_route_on_mounted_server(self, client, mounted_mcp)
⋮----
"""Test disabling a tool on a mounted server via the parent server's HTTP route."""
# Enable the tool on the sub-server
⋮----
# Disable via parent
response = client.post("/tools/sub_mounted_tool/disable")
⋮----
async def test_enable_resource_route_on_mounted_server(self, client, mounted_mcp)
⋮----
resource = await mounted_mcp._resource_manager.get_resource(
⋮----
response = client.post("/resources/data://sub/mounted_resource/enable")
⋮----
async def test_disable_resource_route_on_mounted_server(self, client, mounted_mcp)
⋮----
response = client.post("/resources/data://sub/mounted_resource/disable")
⋮----
async def test_enable_template_route_on_mounted_server(self, client, mounted_mcp)
⋮----
key = "data://mounted_resource/{id}"
resource = mounted_mcp._resource_manager._templates[key]
⋮----
response = client.post("/resources/data://sub/mounted_resource/{id}/enable")
⋮----
async def test_disable_template_route_on_mounted_server(self, client, mounted_mcp)
⋮----
response = client.post("/resources/data://sub/mounted_resource/{id}/disable")
⋮----
async def test_enable_prompt_route_on_mounted_server(self, client, mounted_mcp)
⋮----
"""Test enabling a prompt on a mounted server via the parent server's HTTP route."""
prompt = await mounted_mcp._prompt_manager.get_prompt("mounted_prompt")
⋮----
response = client.post("/prompts/sub_mounted_prompt/enable")
⋮----
async def test_disable_prompt_route_on_mounted_server(self, client, mounted_mcp)
⋮----
"""Test disabling a prompt on a mounted server via the parent server's HTTP route."""
⋮----
response = client.post("/prompts/sub_mounted_prompt/disable")
⋮----
def test_enable_nonexistent_tool(self, client)
⋮----
"""Test enabling a non-existent tool returns 404."""
response = client.post("/tools/nonexistent_tool/enable")
⋮----
def test_disable_nonexistent_tool(self, client)
⋮----
"""Test disabling a non-existent tool returns 404."""
response = client.post("/tools/nonexistent_tool/disable")
⋮----
def test_enable_nonexistent_resource(self, client)
⋮----
"""Test enabling a non-existent resource returns 404."""
response = client.post("/resources/nonexistent://resource/enable")
⋮----
def test_disable_nonexistent_resource(self, client)
⋮----
"""Test disabling a non-existent resource returns 404."""
response = client.post("/resources/nonexistent://resource/disable")
⋮----
def test_enable_nonexistent_prompt(self, client)
⋮----
"""Test enabling a non-existent prompt returns 404."""
response = client.post("/prompts/nonexistent_prompt/enable")
⋮----
def test_disable_nonexistent_prompt(self, client)
⋮----
"""Test disabling a non-existent prompt returns 404."""
response = client.post("/prompts/nonexistent_prompt/disable")
⋮----
class TestAuthComponentManagementRoutes
⋮----
"""Test the component management routes with authentication for tools, resources, and prompts."""
def setup_method(self)
⋮----
"""Set up test fixtures."""
# Generate a key pair and create an auth provider
key_pair = RSAKeyPair.generate()
⋮----
# Add test components
⋮----
@self.mcp.tool
        def test_tool() -> str
⋮----
"""Test tool for auth testing."""
⋮----
@self.mcp.resource("data://test_resource")
        def test_resource() -> str
⋮----
"""Test resource for auth testing."""
⋮----
@self.mcp.prompt
        def test_prompt() -> str
⋮----
"""Test prompt for auth testing."""
⋮----
# Create test client
⋮----
async def test_unauthorized_enable_tool(self)
⋮----
"""Test that unauthenticated requests to enable a tool are rejected."""
tool = await self.mcp._tool_manager.get_tool("test_tool")
⋮----
response = self.client.post("/tools/test_tool/enable")
⋮----
async def test_authorized_enable_tool(self)
⋮----
"""Test that authenticated requests to enable a tool are allowed."""
⋮----
response = self.client.post(
⋮----
async def test_unauthorized_disable_tool(self)
⋮----
"""Test that unauthenticated requests to disable a tool are rejected."""
⋮----
response = self.client.post("/tools/test_tool/disable")
⋮----
async def test_authorized_disable_tool(self)
⋮----
"""Test that authenticated requests to disable a tool are allowed."""
⋮----
async def test_forbidden_enable_tool(self)
⋮----
"""Test that unauthenticated requests to enable a resource are rejected."""
⋮----
async def test_authorized_enable_resource(self)
⋮----
"""Test that authenticated requests to enable a resource are allowed."""
resource = await self.mcp._resource_manager.get_resource("data://test_resource")
⋮----
async def test_unauthorized_disable_resource(self)
⋮----
"""Test that unauthenticated requests to disable a resource are rejected."""
⋮----
response = self.client.post("/resources/data://test_resource/disable")
⋮----
async def test_forbidden_enable_resource(self)
async def test_authorized_disable_resource(self)
⋮----
"""Test that authenticated requests to disable a resource are allowed."""
⋮----
async def test_unauthorized_enable_prompt(self)
⋮----
"""Test that unauthenticated requests to enable a prompt are rejected."""
prompt = await self.mcp._prompt_manager.get_prompt("test_prompt")
⋮----
response = self.client.post("/prompts/test_prompt/enable")
⋮----
async def test_authorized_enable_prompt(self)
⋮----
"""Test that authenticated requests to enable a prompt are allowed."""
⋮----
async def test_unauthorized_disable_prompt(self)
⋮----
"""Test that unauthenticated requests to disable a prompt are rejected."""
⋮----
response = self.client.post("/prompts/test_prompt/disable")
⋮----
async def test_forbidden_disable_prompt(self)
async def test_authorized_disable_prompt(self)
⋮----
"""Test that authenticated requests to disable a prompt are allowed."""
⋮----
class TestComponentManagerWithPath
⋮----
"""Test component manager routes when mounted at a custom path."""
⋮----
@pytest.fixture
    def mcp_with_path(self)
⋮----
mcp = FastMCP("TestServerWithPath")
⋮----
@pytest.fixture
    def client_with_path(self, mcp_with_path)
⋮----
@pytest.mark.asyncio
    async def test_enable_tool_route_with_path(self, client_with_path, mcp_with_path)
⋮----
tool = await mcp_with_path._tool_manager.get_tool("test_tool")
⋮----
response = client_with_path.post("/test/tools/test_tool/enable")
⋮----
resource = await mcp_with_path._resource_manager.get_resource(
⋮----
response = client_with_path.post("/test/resources/data://test_resource/disable")
⋮----
@pytest.mark.asyncio
    async def test_enable_prompt_route_with_path(self, client_with_path, mcp_with_path)
⋮----
prompt = await mcp_with_path._prompt_manager.get_prompt("test_prompt")
⋮----
response = client_with_path.post("/test/prompts/test_prompt/enable")
⋮----
class TestComponentManagerWithPathAuth
⋮----
"""Test component manager routes with auth when mounted at a custom path."""
⋮----
@pytest.mark.asyncio
    async def test_unauthorized_enable_tool(self)
⋮----
response = self.client.post("/test/tools/test_tool/enable")
⋮----
@pytest.mark.asyncio
    async def test_forbidden_enable_tool(self)
⋮----
@pytest.mark.asyncio
    async def test_authorized_enable_tool(self)
⋮----
@pytest.mark.asyncio
    async def test_unauthorized_disable_resource(self)
⋮----
response = self.client.post("/test/resources/data://test_resource/disable")
⋮----
@pytest.mark.asyncio
    async def test_forbidden_disable_resource(self)
⋮----
@pytest.mark.asyncio
    async def test_authorized_disable_resource(self)
⋮----
@pytest.mark.asyncio
    async def test_unauthorized_enable_prompt(self)
⋮----
response = self.client.post("/test/prompts/test_prompt/enable")
⋮----
@pytest.mark.asyncio
    async def test_forbidden_enable_prompt(self)
⋮----
@pytest.mark.asyncio
    async def test_authorized_enable_prompt(self)

================
File: tests/contrib/test_mcp_mixin.py
================
"""Tests for the MCPMixin class."""
⋮----
class TestMCPMixin
⋮----
"""Test suite for MCPMixin functionality."""
def test_initialization(self)
⋮----
"""Test that a class inheriting MCPMixin can be initialized."""
class MyMixin(MCPMixin)
instance = MyMixin()
⋮----
# --- Tool Registration Tests ---
⋮----
"""Test tool registration with prefix and separator variations."""
mcp = FastMCP()
class MyToolMixin(MCPMixin)
⋮----
@mcp_tool()
            def sample_tool(self)
instance = MyToolMixin()
⋮----
registered_tools = await mcp.get_tools()
⋮----
"""Test resource registration with prefix and separator variations."""
⋮----
class MyResourceMixin(MCPMixin)
⋮----
@mcp_resource(uri="test://resource")
            def sample_resource(self)
instance = MyResourceMixin()
⋮----
registered_resources = await mcp.get_resources()
⋮----
"""Test prompt registration with prefix and separator variations."""
⋮----
class MyPromptMixin(MCPMixin)
⋮----
@mcp_prompt()
            def sample_prompt(self)
instance = MyPromptMixin()
⋮----
prompts = await mcp.get_prompts()
⋮----
async def test_register_all_no_prefix(self)
⋮----
"""Test register_all method registers all types without a prefix."""
⋮----
class MyFullMixin(MCPMixin)
⋮----
@mcp_tool()
            def tool_all(self)
⋮----
@mcp_resource(uri="res://all")
            def resource_all(self)
⋮----
@mcp_prompt()
            def prompt_all(self)
instance = MyFullMixin()
⋮----
tools = await mcp.get_tools()
resources = await mcp.get_resources()
⋮----
async def test_register_all_with_prefix_default_separators(self)
⋮----
"""Test register_all method registers all types with a prefix and default separators."""
⋮----
class MyFullMixinPrefixed(MCPMixin)
⋮----
@mcp_tool()
            def tool_all_p(self)
⋮----
@mcp_resource(uri="res://all_p")
            def resource_all_p(self)
⋮----
@mcp_prompt()
            def prompt_all_p(self)
instance = MyFullMixinPrefixed()
⋮----
async def test_register_all_with_prefix_custom_separators(self)
⋮----
"""Test register_all method registers all types with a prefix and custom separators."""
⋮----
class MyFullMixinCustomSep(MCPMixin)
⋮----
@mcp_tool()
            def tool_cust(self)
⋮----
@mcp_resource(uri="res://cust")
            def resource_cust(self)
⋮----
@mcp_prompt()
            def prompt_cust(self)
instance = MyFullMixinCustomSep()
⋮----
# Check default separators weren't used

================
File: tests/deprecated/__init__.py
================
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")

================
File: tests/deprecated/test_deprecated.py
================
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
class TestDeprecationWarningsSetting
⋮----
def test_deprecation_warnings_setting_true(self)
⋮----
# will warn once for providing deprecated arg
mcp = FastMCP(host="1.2.3.4")
# will warn once for accessing deprecated property
⋮----
def test_deprecation_warnings_setting_false(self)
⋮----
# will error if a warning is raised
⋮----
def test_sse_app_deprecation_warning()
⋮----
"""Test that sse_app raises a deprecation warning."""
server = FastMCP("TestServer")
⋮----
app = server.sse_app()
⋮----
def test_streamable_http_app_deprecation_warning()
⋮----
"""Test that streamable_http_app raises a deprecation warning."""
⋮----
app = server.streamable_http_app()
⋮----
async def test_run_sse_async_deprecation_warning()
⋮----
"""Test that run_sse_async raises a deprecation warning."""
⋮----
# Use patch to avoid actually running the server
⋮----
# Verify the mock was called with the right transport
⋮----
call_kwargs = mock_run.call_args.kwargs
⋮----
async def test_run_streamable_http_async_deprecation_warning()
⋮----
"""Test that run_streamable_http_async raises a deprecation warning."""
⋮----
def test_http_app_with_sse_transport()
⋮----
"""Test that http_app with SSE transport works (no warning)."""
⋮----
# This should not raise a warning since we're using the new API
⋮----
app = server.http_app(transport="sse")
⋮----
# Verify no deprecation warnings were raised for using transport parameter
deprecation_warnings = [
⋮----
def test_from_client_deprecation_warning()
⋮----
"""Test that FastMCP.from_client raises a deprecation warning."""

================
File: tests/deprecated/test_mount_import_arg_order.py
================
class TestDeprecatedMountArgOrder
⋮----
"""Test deprecated positional argument order for mount() method."""
async def test_mount_deprecated_arg_order_with_warning(self)
⋮----
"""Test that mount(prefix, server) still works but raises deprecation warning."""
main_app = FastMCP("MainApp")
sub_app = FastMCP("SubApp")
⋮----
@sub_app.tool
        def sub_tool() -> str
# Test the deprecated argument order: mount(prefix, server)
⋮----
main_app.mount("sub", sub_app)  # type: ignore[arg-type]  # Old order: prefix first, server second
# Check that a deprecation warning was raised
⋮----
# Verify the mount worked correctly despite deprecated order
tools = await main_app.get_tools()
⋮----
# Test functionality
⋮----
result = await client.call_tool("sub_sub_tool", {})
⋮----
async def test_mount_new_arg_order_no_warning(self)
⋮----
"""Test that mount(server, prefix) works without deprecation warning."""
⋮----
# Test the new argument order: mount(server, prefix)
⋮----
main_app.mount(sub_app, "sub")  # New order: server first, prefix second
# Check that no deprecation warning was raised for argument order
mount_warnings = [
⋮----
# Verify the mount worked correctly
⋮----
async def test_mount_deprecated_order_no_prefix(self)
⋮----
"""Test deprecated order detection when first arg is empty string."""
⋮----
# Test with empty string as first argument (old style for no prefix)
⋮----
main_app.mount("", sub_app)  # type: ignore[arg-type]  # Old order: empty prefix first, server second
⋮----
# Verify the mount worked correctly (no prefix)
⋮----
assert "sub_tool" in tools  # No prefix applied
class TestDeprecatedImportArgOrder
⋮----
"""Test deprecated positional argument order for import_server() method."""
async def test_import_deprecated_arg_order_with_warning(self)
⋮----
"""Test that import_server(prefix, server) still works but raises deprecation warning."""
⋮----
# Test the deprecated argument order: import_server(prefix, server)
⋮----
await main_app.import_server("sub", sub_app)  # type: ignore[arg-type]  # Old order: prefix first, server second
⋮----
# Verify the import worked correctly despite deprecated order
⋮----
async def test_import_new_arg_order_no_warning(self)
⋮----
"""Test that import_server(server, prefix) works without deprecation warning."""
⋮----
# Test the new argument order: import_server(server, prefix)
⋮----
)  # New order: server first, prefix second
⋮----
import_warnings = [
⋮----
# Verify the import worked correctly
⋮----
async def test_import_deprecated_order_no_prefix(self)
⋮----
await main_app.import_server("", sub_app)  # type: ignore[arg-type]  # Old order: empty prefix first, server second
⋮----
# Verify the import worked correctly (no prefix)
assert "sub_tool" in main_app._tool_manager._tools  # No prefix applied
async def test_import_deprecated_order_with_resources_and_prompts(self)
⋮----
"""Test deprecated order works with all component types."""
⋮----
@sub_app.resource(uri="data://config")
        def sub_resource()
⋮----
@sub_app.resource(uri="users://{user_id}/info")
        def sub_template(user_id: str)
⋮----
@sub_app.prompt
        def sub_prompt() -> str
# Test the deprecated argument order with all component types
⋮----
await main_app.import_server("api", sub_app)  # type: ignore[arg-type]  # Old order: prefix first, server second
⋮----
# Verify all component types were imported correctly with prefix
⋮----
class TestArgOrderDetection
⋮----
"""Test that argument order detection works correctly."""
async def test_mount_correctly_identifies_server_vs_string(self)
⋮----
"""Test that mount correctly identifies FastMCP instances vs strings."""
⋮----
# This should NOT trigger deprecation warning (server first, prefix second)
⋮----
# This SHOULD trigger deprecation warning (string first, server second)
⋮----
main_app.mount("prefix2", sub_app)  # type: ignore[arg-type]
⋮----
async def test_import_correctly_identifies_server_vs_string(self)
⋮----
"""Test that import_server correctly identifies FastMCP instances vs strings."""
⋮----
await main_app.import_server("prefix2", sub_app)  # type: ignore[arg-type]

================
File: tests/deprecated/test_mount_separators.py
================
"""Tests for the deprecated separator parameters in mount() and import_server() methods."""
⋮----
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
def test_mount_resource_separator_deprecation_warning()
⋮----
"""Test that using resource_separator in mount() raises a deprecation warning."""
main_app = FastMCP("MainApp")
sub_app = FastMCP("SubApp")
⋮----
main_app.mount("sub", sub_app, resource_separator="+")  # type: ignore[arg-type]
# Check that we get both the argument order warning and the resource_separator warning
warning_messages = [str(w.message) for w in warnings]
⋮----
async def test_mount_tool_separator_deprecation_warning()
⋮----
"""Test that using tool_separator in mount() raises a deprecation warning."""
⋮----
main_app.mount("sub", sub_app, tool_separator="-")  # type: ignore[arg-type]
# Check that we get both the argument order warning and the tool_separator warning
⋮----
# Verify the separator is ignored and the default is used
⋮----
@sub_app.tool
    def test_tool()
⋮----
async def test_mount_prompt_separator_deprecation_warning()
⋮----
"""Test that using prompt_separator in mount() raises a deprecation warning."""
⋮----
main_app.mount("sub", sub_app, prompt_separator="-")  # type: ignore[arg-type]
# Check that we get both the argument order warning and the prompt_separator warning
⋮----
@sub_app.prompt
    def test_prompt()
⋮----
async def test_import_server_separator_deprecation_warnings()
⋮----
"""Test that using separators in import_server() raises deprecation warnings."""
⋮----
await main_app.import_server("sub", sub_app, tool_separator="-")  # type: ignore[arg-type]
# Check that we get both warnings
⋮----
await main_app.import_server("sub", sub_app, resource_separator="+")  # type: ignore[arg-type]
⋮----
await main_app.import_server("sub", sub_app, prompt_separator="-")  # type: ignore[arg-type]

================
File: tests/deprecated/test_resource_prefixes.py
================
"""Tests for legacy resource prefix behavior."""
⋮----
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
class TestLegacyResourcePrefixes
⋮----
"""Test the legacy resource prefix behavior."""
def test_add_resource_prefix_legacy(self)
⋮----
"""Test that add_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
⋮----
result = add_resource_prefix("resource://path/to/resource", "prefix")
⋮----
# Empty prefix should return the original URI
result = add_resource_prefix("resource://path/to/resource", "")
⋮----
def test_remove_resource_prefix_legacy(self)
⋮----
"""Test that remove_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
⋮----
result = remove_resource_prefix(
⋮----
# URI without the prefix should be returned as is
result = remove_resource_prefix("resource://path/to/resource", "prefix")
⋮----
result = remove_resource_prefix("resource://path/to/resource", "")
⋮----
def test_has_resource_prefix_legacy(self)
⋮----
"""Test that has_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
⋮----
result = has_resource_prefix("prefix+resource://path/to/resource", "prefix")
⋮----
result = has_resource_prefix("resource://path/to/resource", "prefix")
⋮----
# Empty prefix should always return False
result = has_resource_prefix("resource://path/to/resource", "")
⋮----
async def test_mount_with_legacy_prefixes()
⋮----
"""Test mounting a server with legacy resource prefixes."""
⋮----
main_server = FastMCP("MainServer")
sub_server = FastMCP("SubServer")
⋮----
@sub_server.resource("resource://test")
        def get_test()
# Mount the server with a prefix (using old argument order for this legacy test)
⋮----
main_server.mount("sub", sub_server)  # type: ignore[arg-type]
# Check that the resource is prefixed using the legacy format
resources = await main_server.get_resources()
# In legacy format, the key would be "sub+resource://test"
⋮----
# Test accessing the resource through client
⋮----
result = await client.read_resource("sub+resource://test")
# Different content types might be returned, but we just want to verify we got something
⋮----
async def test_import_server_with_legacy_prefixes()
⋮----
"""Test importing a server with legacy resource prefixes."""
⋮----
# Import the server with a prefix (using old argument order for this legacy test)
⋮----
await main_server.import_server("sub", sub_server)  # type: ignore[arg-type]

================
File: tests/deprecated/test_route_type_ignore.py
================
"""Tests for the deprecated RouteType.IGNORE."""
⋮----
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
def test_route_type_ignore_deprecation_warning()
⋮----
"""Test that using RouteType.IGNORE emits a deprecation warning."""
# Let's manually capture the warnings
# Record all warnings
⋮----
# Make sure warnings are always triggered
⋮----
# Create a RouteMap with RouteType.IGNORE
route_map = RouteMap(
# Check for the expected warnings in the recorded warnings
route_type_warning = False
ignore_warning = False
⋮----
message = str(w.message)
⋮----
route_type_warning = True
⋮----
ignore_warning = True
# Make sure both warnings were triggered
⋮----
# Verify that RouteType.IGNORE was converted to MCPType.EXCLUDE
⋮----
class TestRouteTypeIgnoreDeprecation
⋮----
"""Test class for the deprecated RouteType.IGNORE."""
⋮----
@pytest.fixture
    def basic_openapi_spec(self) -> dict
⋮----
"""Create a simple OpenAPI spec for testing."""
⋮----
@pytest.fixture
    async def mock_client(self) -> httpx.AsyncClient
⋮----
"""Create a mock client for testing."""
async def _responder(request)
⋮----
async def test_route_type_ignore_conversion(self, basic_openapi_spec, mock_client)
⋮----
"""Test that routes with RouteType.IGNORE are properly excluded."""
# Capture the deprecation warning without checking the exact message
⋮----
server = FastMCPOpenAPI(
⋮----
# Use the deprecated RouteType.IGNORE
⋮----
# Make everything else a resource
⋮----
# Check that the analytics route was excluded (converted from IGNORE to EXCLUDE)
resources = await server.get_resources()
resource_uris = [str(r.uri) for r in resources.values()]
# Analytics should be excluded

================
File: tests/deprecated/test_settings.py
================
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
class TestDeprecatedServerInitKwargs
⋮----
"""Test deprecated server initialization keyword arguments."""
def test_log_level_deprecation_warning(self)
⋮----
"""Test that log_level raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", log_level="DEBUG")
# Verify the setting is still applied
⋮----
def test_debug_deprecation_warning(self)
⋮----
"""Test that debug raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", debug=True)
⋮----
def test_host_deprecation_warning(self)
⋮----
"""Test that host raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", host="0.0.0.0")
⋮----
def test_port_deprecation_warning(self)
⋮----
"""Test that port raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", port=8080)
⋮----
def test_sse_path_deprecation_warning(self)
⋮----
"""Test that sse_path raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", sse_path="/custom-sse")
⋮----
def test_message_path_deprecation_warning(self)
⋮----
"""Test that message_path raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", message_path="/custom-message")
⋮----
def test_streamable_http_path_deprecation_warning(self)
⋮----
"""Test that streamable_http_path raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", streamable_http_path="/custom-http")
⋮----
def test_json_response_deprecation_warning(self)
⋮----
"""Test that json_response raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", json_response=True)
⋮----
def test_stateless_http_deprecation_warning(self)
⋮----
"""Test that stateless_http raises a deprecation warning."""
⋮----
server = FastMCP("TestServer", stateless_http=True)
⋮----
def test_multiple_deprecated_kwargs_warnings(self)
⋮----
"""Test that multiple deprecated kwargs each raise their own warning."""
⋮----
server = FastMCP(
# Should have 9 deprecation warnings (one for each deprecated parameter)
deprecation_warnings = [
⋮----
# Verify all expected parameters are mentioned in warnings
expected_params = {
mentioned_params = set()
⋮----
message = str(warning.message)
⋮----
# Verify all settings are still applied
⋮----
def test_non_deprecated_kwargs_no_warnings(self)
⋮----
"""Test that non-deprecated kwargs don't raise warnings."""
⋮----
# Should have no deprecation warnings
⋮----
# Verify server was created successfully
⋮----
def test_none_values_no_warnings(self)
⋮----
"""Test that None values for deprecated kwargs don't raise warnings."""
⋮----
# Should have no deprecation warnings for None values
⋮----
def test_deprecated_settings_inheritance_from_global(self)
⋮----
"""Test that deprecated settings inherit from global settings when not provided."""
# Mock fastmcp.settings to test inheritance
⋮----
server = FastMCP("TestServer")
# Verify settings are inherited from global settings
⋮----
def test_deprecated_settings_override_global(self)
⋮----
"""Test that deprecated settings override global settings when provided."""
# Mock fastmcp.settings to test override behavior
⋮----
warnings.simplefilter("ignore")  # Ignore warnings for this test
⋮----
# Verify provided settings override global settings
⋮----
# Non-overridden settings should still come from global
⋮----
def test_stacklevel_points_to_constructor_call(self)
⋮----
"""Test that deprecation warnings point to the FastMCP constructor call."""
⋮----
# Should have exactly one deprecation warning
⋮----
# The warning should point to the server.py file where FastMCP.__init__ is called
# This verifies the stacklevel is working as intended (pointing to constructor)
warning = deprecation_warnings[0]
⋮----
class TestDeprecatedEnvironmentVariables
⋮----
"""Test deprecated environment variable prefixes."""
def test_fastmcp_server_env_var_deprecation_warning(self, caplog)
⋮----
"""Test that FASTMCP_SERVER_ environment variables emit deprecation warnings."""
env_var_name = "FASTMCP_SERVER_HOST"
original_value = os.environ.get(env_var_name)
⋮----
settings = Settings()
# Check that a warning was logged
⋮----
# Verify the setting is still applied
⋮----
# Clean up environment variable
⋮----
class TestDeprecatedSettingsProperty
⋮----
"""Test deprecated settings property access."""
def test_settings_property_deprecation_warning(self, caplog)
⋮----
"""Test that accessing fastmcp.settings.settings logs a deprecation warning."""
⋮----
# Access the deprecated property
deprecated_settings = settings.settings
# Check that a warning was logged
⋮----
# Verify it still returns the same settings object

================
File: tests/prompts/test_prompt_manager.py
================
class TestPromptManager
⋮----
async def test_add_prompt(self)
⋮----
"""Test adding a prompt to the manager."""
def fn() -> str
manager = PromptManager()
prompt = Prompt.from_function(fn)
added = manager.add_prompt(prompt)
⋮----
async def test_add_duplicate_prompt(self, caplog)
⋮----
"""Test adding the same prompt twice."""
⋮----
manager = PromptManager(duplicate_behavior="warn")
⋮----
first = manager.add_prompt(prompt)
second = manager.add_prompt(prompt)
⋮----
async def test_disable_warn_on_duplicate_prompts(self, caplog)
⋮----
"""Test disabling warning on duplicate prompts."""
⋮----
manager = PromptManager(duplicate_behavior="ignore")
⋮----
async def test_warn_on_duplicate_prompts(self, caplog)
⋮----
"""Test warning on duplicate prompts."""
⋮----
def test_fn() -> str
prompt = Prompt.from_function(test_fn, name="test_prompt")
⋮----
# Should have the prompt
⋮----
async def test_error_on_duplicate_prompts(self)
⋮----
"""Test error on duplicate prompts."""
manager = PromptManager(duplicate_behavior="error")
⋮----
async def test_replace_duplicate_prompts(self)
⋮----
"""Test replacing duplicate prompts."""
manager = PromptManager(duplicate_behavior="replace")
def original_fn() -> str
def replacement_fn() -> str
prompt1 = Prompt.from_function(original_fn, name="test_prompt")
prompt2 = Prompt.from_function(replacement_fn, name="test_prompt")
⋮----
# Should have replaced with the new prompt
prompt = await manager.get_prompt("test_prompt")
⋮----
async def test_ignore_duplicate_prompts(self)
⋮----
"""Test ignoring duplicate prompts."""
⋮----
result = manager.add_prompt(prompt2)
# Should keep the original
⋮----
# Result should be the original prompt
⋮----
async def test_get_prompts(self)
⋮----
"""Test retrieving all prompts."""
def fn1() -> str
def fn2() -> str
⋮----
prompt1 = Prompt.from_function(fn1)
prompt2 = Prompt.from_function(fn2)
⋮----
prompts = await manager.get_prompts()
⋮----
class TestRenderPrompt
⋮----
async def test_render_prompt(self)
⋮----
"""Test rendering a prompt."""
⋮----
"""An example prompt."""
⋮----
result = await manager.render_prompt("fn")
⋮----
async def test_render_prompt_with_args(self)
⋮----
"""Test rendering a prompt with arguments."""
def fn(name: str) -> str
⋮----
result = await manager.render_prompt("fn", arguments={"name": "World"})
⋮----
async def test_render_prompt_callable_object(self)
⋮----
"""Test rendering a prompt with a callable object."""
class MyPrompt
⋮----
"""A callable object that can be used as a prompt."""
def __call__(self, name: str) -> str
⋮----
"""ignore this"""
⋮----
prompt = Prompt.from_function(MyPrompt())
⋮----
result = await manager.render_prompt("MyPrompt", arguments={"name": "World"})
⋮----
async def test_render_prompt_callable_object_async(self)
⋮----
async def __call__(self, name: str) -> str
⋮----
async def test_render_unknown_prompt(self)
⋮----
"""Test rendering a non-existent prompt."""
⋮----
async def test_render_prompt_with_missing_args(self)
⋮----
"""Test rendering a prompt with missing required arguments."""
⋮----
async def test_prompt_with_varargs_not_allowed(self)
⋮----
"""Test that a prompt with *args is not allowed."""
def fn(*args: int) -> str
⋮----
async def test_prompt_with_varkwargs_not_allowed(self)
⋮----
"""Test that a prompt with **kwargs is not allowed."""
def fn(**kwargs: int) -> str
⋮----
class TestPromptTags
⋮----
"""Test functionality related to prompt tags."""
async def test_add_prompt_with_tags(self)
⋮----
"""Test adding a prompt with tags."""
def greeting() -> str
⋮----
prompt = Prompt.from_function(greeting, tags={"greeting", "simple"})
⋮----
prompt = await manager.get_prompt("greeting")
⋮----
async def test_add_prompt_with_empty_tags(self)
⋮----
"""Test adding a prompt with empty tags."""
⋮----
prompt = Prompt.from_function(greeting, tags=set())
⋮----
async def test_add_prompt_with_none_tags(self)
⋮----
"""Test adding a prompt with None tags."""
⋮----
prompt = Prompt.from_function(greeting, tags=None)
⋮----
async def test_list_prompts_with_tags(self)
⋮----
"""Test listing prompts with specific tags."""
⋮----
def weather(location: str) -> str
def summary(text: str) -> str
⋮----
# Filter prompts by tags
⋮----
simple_prompts = [p for p in prompts.values() if "simple" in p.tags]
⋮----
nlp_prompts = [p for p in prompts.values() if "nlp" in p.tags]
⋮----
class TestContextHandling
⋮----
"""Test context handling in prompts."""
def test_context_parameter_detection(self)
⋮----
"""Test that context parameters are properly detected in
        Prompt.from_function()."""
def prompt_with_context(x: int, ctx: Context) -> str
⋮----
def prompt_without_context(x: int) -> str
⋮----
def test_parameterized_context_parameter_detection(self)
⋮----
"""Test that parameterized context parameters are properly detected in
        Prompt.from_function()."""
⋮----
def test_parameterized_union_context_parameter_detection(self)
⋮----
"""Test that context parameters in a union are properly detected in
        Prompt.from_function()."""
def prompt_with_context(x: int, ctx: Context | None) -> str
⋮----
async def test_context_injection(self)
⋮----
"""Test that context is properly injected during prompt rendering."""
⋮----
prompt = Prompt.from_function(prompt_with_context)
⋮----
mcp = FastMCP()
context = Context(fastmcp=mcp)
⋮----
messages = await prompt.render(arguments={"x": 42})
⋮----
assert messages[0].content.text == "42"  # type: ignore[attr-defined]
async def test_context_optional(self)
⋮----
"""Test that context is optional when rendering prompts."""
def prompt_with_context(x: int, ctx: Context | None = None) -> str
⋮----
# Even for optional context, we need to provide a context
⋮----
messages = await prompt.render(
⋮----
async def test_annotated_context_parameter_detection(self)
⋮----
"""Test that annotated context parameters are properly detected in
        Prompt.from_function()."""
def prompt_with_context(x: int, ctx: Annotated[Context, "ctx"]) -> str

================
File: tests/prompts/test_prompt.py
================
class TestRenderPrompt
⋮----
async def test_basic_fn(self)
⋮----
def fn() -> str
prompt = Prompt.from_function(fn)
⋮----
async def test_async_fn(self)
⋮----
async def fn() -> str
⋮----
async def test_fn_with_args(self)
⋮----
async def fn(name: str, age: int = 30) -> str
⋮----
async def test_callable_object(self)
⋮----
class MyPrompt
⋮----
def __call__(self, name: str) -> str
prompt = Prompt.from_function(MyPrompt())
⋮----
async def test_async_callable_object(self)
⋮----
async def __call__(self, name: str) -> str
⋮----
async def test_fn_with_invalid_kwargs(self)
async def test_fn_returns_message(self)
⋮----
async def fn() -> PromptMessage
⋮----
async def test_fn_returns_assistant_message(self)
async def test_fn_returns_multiple_messages(self)
⋮----
expected = [
async def fn() -> list[PromptMessage]
⋮----
async def test_fn_returns_list_of_strings(self)
⋮----
async def fn() -> list[str]
⋮----
async def test_fn_returns_resource_content(self)
⋮----
"""Test returning a message with resource content."""
⋮----
async def test_fn_returns_mixed_content(self)
⋮----
"""Test returning messages with mixed content types."""
async def fn() -> list[PromptMessage | str]
⋮----
async def test_fn_returns_message_with_resource(self)
⋮----
"""Test returning a dict with resource content."""
⋮----
class TestPromptTypeConversion
⋮----
async def test_list_of_integers_as_string_args(self)
⋮----
"""Test that prompts can handle complex types passed as strings from MCP spec."""
def sum_numbers(numbers: list[int]) -> str
⋮----
"""Calculate the sum of a list of numbers."""
total = sum(numbers)
⋮----
prompt = Prompt.from_function(sum_numbers)
# MCP spec only allows string arguments, so this should work
# after we implement type conversion
result_from_string = await prompt.render(
⋮----
# Both should work now with string conversion
result_from_list_string = await prompt.render(
⋮----
async def test_various_type_conversions(self)
⋮----
"""Test type conversion for various data types."""
⋮----
prompt = Prompt.from_function(process_data)
# All arguments as strings (as MCP would send them)
result = await prompt.render(
expected_text = (
⋮----
async def test_type_conversion_error_handling(self)
⋮----
"""Test that informative errors are raised for invalid type conversions."""
⋮----
def typed_prompt(numbers: list[int]) -> str
prompt = Prompt.from_function(typed_prompt)
# Test with invalid JSON - should raise PromptError due to exception handling in render()
⋮----
async def test_json_parsing_fallback(self)
⋮----
"""Test that JSON parsing falls back to direct validation when needed."""
def data_prompt(value: int) -> str
prompt = Prompt.from_function(data_prompt)
# This should work with JSON parsing (integer as string)
result1 = await prompt.render(arguments={"value": "42"})
⋮----
# This should work with direct validation (already an integer string)
result2 = await prompt.render(arguments={"value": "123"})
⋮----
async def test_mixed_string_and_typed_args(self)
⋮----
"""Test mixing string args (no conversion) with typed args (conversion needed)."""
def mixed_prompt(message: str, count: int) -> str
prompt = Prompt.from_function(mixed_prompt)
⋮----
"message": "Hello world",  # str - no conversion needed
"count": "3",  # int - conversion needed
⋮----
class TestPromptArgumentDescriptions
⋮----
def test_enhanced_descriptions_for_non_string_types(self)
⋮----
"""Test that non-string argument types get enhanced descriptions with JSON schema."""
⋮----
"""Analyze numerical data."""
⋮----
prompt = Prompt.from_function(analyze_data)
⋮----
# Check that string parameter has no schema enhancement
name_arg = next((arg for arg in prompt.arguments if arg.name == "name"), None)
⋮----
assert name_arg.description is None  # No enhancement for string types
# Check that non-string parameters have schema enhancements
numbers_arg = next(
⋮----
metadata_arg = next(
⋮----
threshold_arg = next(
⋮----
active_arg = next(
⋮----
def test_enhanced_descriptions_with_existing_descriptions(self)
⋮----
"""Test that existing parameter descriptions are preserved with schema appended."""
⋮----
"""Process numbers."""
⋮----
prompt = Prompt.from_function(documented_prompt)
⋮----
# Should have both the original description and the schema
⋮----
assert "\n\n" in numbers_arg.description  # Should have newline separator
⋮----
def test_string_parameters_no_enhancement(self)
⋮----
"""Test that string parameters don't get schema enhancement."""
def string_only_prompt(message: str, name: str) -> str
prompt = Prompt.from_function(string_only_prompt)
⋮----
# String parameters should not have schema enhancement

================
File: tests/resources/test_file_resources.py
================
@pytest.fixture
def temp_file()
⋮----
"""Create a temporary file for testing.
    File is automatically cleaned up after the test if it still exists.
    """
content = "test content"
⋮----
path = Path(f.name).resolve()
⋮----
pass  # File was already deleted by the test
class TestFileResource
⋮----
"""Test FileResource functionality."""
def test_file_resource_creation(self, temp_file: Path)
⋮----
"""Test creating a FileResource."""
resource = FileResource(
⋮----
assert resource.mime_type == "text/plain"  # default
⋮----
assert resource.is_binary is False  # default
def test_file_resource_str_path_conversion(self, temp_file: Path)
⋮----
"""Test FileResource handles string paths."""
⋮----
async def test_read_text_file(self, temp_file: Path)
⋮----
"""Test reading a text file."""
⋮----
content = await resource.read()
⋮----
async def test_read_binary_file(self, temp_file: Path)
⋮----
"""Test reading a file as binary."""
⋮----
def test_relative_path_error(self)
⋮----
"""Test error on relative path."""
⋮----
async def test_missing_file_error(self, temp_file: Path)
⋮----
"""Test error when file doesn't exist."""
# Create path to non-existent file
missing = temp_file.parent / "missing.txt"
⋮----
async def test_permission_error(self, temp_file: Path)
⋮----
"""Test reading a file without permissions."""
temp_file.chmod(0o000)  # Remove all permissions
⋮----
temp_file.chmod(0o644)  # Restore permissions

================
File: tests/resources/test_function_resources.py
================
class TestFunctionResource
⋮----
"""Test FunctionResource functionality."""
def test_function_resource_creation(self)
⋮----
"""Test creating a FunctionResource."""
def my_func() -> str
resource = FunctionResource(
⋮----
assert resource.mime_type == "text/plain"  # default
⋮----
async def test_read_text(self)
⋮----
"""Test reading text from a FunctionResource."""
def get_data() -> str
⋮----
content = await resource.read()
⋮----
async def test_read_binary(self)
⋮----
"""Test reading binary data from a FunctionResource."""
def get_data() -> bytes
⋮----
async def test_json_conversion(self)
⋮----
"""Test automatic JSON conversion of non-string results."""
def get_data() -> dict
⋮----
async def test_error_handling(self)
⋮----
"""Test error handling in FunctionResource."""
def failing_func() -> str
⋮----
async def test_basemodel_conversion(self)
⋮----
"""Test handling of BaseModel types."""
class MyModel(BaseModel)
⋮----
name: str
⋮----
async def test_custom_type_conversion(self)
⋮----
"""Test handling of custom types."""
class CustomData
⋮----
def __str__(self) -> str
def get_data() -> CustomData
⋮----
async def test_async_read_text(self)
⋮----
"""Test reading text from async FunctionResource."""
async def get_data() -> str

================
File: tests/resources/test_resource_manager.py
================
@pytest.fixture
def temp_file()
⋮----
"""Create a temporary file for testing.
    File is automatically cleaned up after the test if it still exists.
    """
content = "test content"
⋮----
path = Path(f.name).resolve()
⋮----
pass  # File was already deleted by the test
class TestResourceManager
⋮----
"""Test ResourceManager functionality."""
async def test_add_resource(self, temp_file: Path)
⋮----
"""Test adding a resource."""
manager = ResourceManager()
file_url = "file://test-resource"
resource = FileResource(
added = manager.add_resource(resource)
⋮----
# Get the actual key from the resource manager
resources = await manager.get_resources()
⋮----
async def test_add_duplicate_resource(self, temp_file: Path)
⋮----
"""Test adding the same resource twice."""
⋮----
first = manager.add_resource(resource)
second = manager.add_resource(resource)
⋮----
# Check the resource is there
⋮----
async def test_warn_on_duplicate_resources(self, temp_file: Path, caplog)
⋮----
"""Test warning on duplicate resources."""
manager = ResourceManager(duplicate_behavior="warn")
⋮----
# Should have the resource
⋮----
async def test_disable_warn_on_duplicate_resources(self, temp_file: Path, caplog)
⋮----
"""Test disabling warning on duplicate resources."""
manager = ResourceManager(duplicate_behavior="ignore")
⋮----
async def test_error_on_duplicate_resources(self, temp_file: Path)
⋮----
"""Test error on duplicate resources."""
manager = ResourceManager(duplicate_behavior="error")
⋮----
async def test_replace_duplicate_resources(self, temp_file: Path)
⋮----
"""Test replacing duplicate resources."""
manager = ResourceManager(duplicate_behavior="replace")
⋮----
resource1 = FileResource(
resource2 = FileResource(
⋮----
# Should have replaced with the new resource
⋮----
resource_list = list(resources.values())
⋮----
async def test_ignore_duplicate_resources(self, temp_file: Path)
⋮----
"""Test ignoring duplicate resources."""
⋮----
result = manager.add_resource(resource2)
# Should keep the original
⋮----
# Result should be the original resource
⋮----
async def test_warn_on_duplicate_templates(self, caplog)
⋮----
"""Test warning on duplicate templates."""
⋮----
def template_fn(id: str) -> str
template = ResourceTemplate.from_function(
⋮----
# Should have the template
templates = await manager.get_resource_templates()
⋮----
async def test_error_on_duplicate_templates(self)
⋮----
"""Test error on duplicate templates."""
⋮----
async def test_replace_duplicate_templates(self)
⋮----
"""Test replacing duplicate templates."""
⋮----
def original_fn(id: str) -> str
def replacement_fn(id: str) -> str
template1 = ResourceTemplate.from_function(
template2 = ResourceTemplate.from_function(
⋮----
# Should have replaced with the new template
templates_dict = await manager.get_resource_templates()
templates = list(templates_dict.values())
⋮----
async def test_ignore_duplicate_templates(self)
⋮----
"""Test ignoring duplicate templates."""
⋮----
result = manager.add_template(template2)
⋮----
# Result should be the original template
⋮----
async def test_get_resource(self, temp_file: Path)
⋮----
"""Test getting a resource by URI."""
⋮----
retrieved = await manager.get_resource(resource.uri)
⋮----
async def test_get_resource_from_template(self)
⋮----
"""Test getting a resource through a template."""
⋮----
def greet(name: str) -> str
⋮----
resource = await manager.get_resource(AnyUrl("greet://world"))
⋮----
content = await resource.read()
⋮----
async def test_get_unknown_resource(self)
⋮----
"""Test getting a non-existent resource."""
⋮----
async def test_get_resources(self, temp_file: Path)
⋮----
"""Test retrieving all resources."""
⋮----
file_url1 = "file://test-resource1"
⋮----
file_url2 = "file://test-resource2"
⋮----
values = list(resources.values())
⋮----
class TestResourceTags
⋮----
"""Test functionality related to resource tags."""
async def test_add_resource_with_tags(self, temp_file: Path)
⋮----
"""Test adding a resource with tags."""
⋮----
# Check that tags are preserved
resources_dict = await manager.get_resources()
resources = list(resources_dict.values())
⋮----
async def test_add_function_resource_with_tags(self)
⋮----
"""Test adding a function resource with tags."""
⋮----
async def get_data()
resource = FunctionResource(
⋮----
async def test_add_template_with_tags(self)
⋮----
"""Test adding a resource template with tags."""
⋮----
def user_data(user_id: str) -> str
⋮----
async def test_filter_resources_by_tags(self, temp_file: Path)
⋮----
"""Test filtering resources by tags."""
⋮----
# Create multiple resources with different tags
⋮----
async def get_user_data()
resource2 = FunctionResource(
async def get_system_data()
resource3 = FunctionResource(
⋮----
# Filter by tags
⋮----
data_resources = [r for r in resources_dict.values() if "data" in r.tags]
⋮----
admin_resources = [r for r in resources_dict.values() if "admin" in r.tags]
⋮----
class TestCustomResourceKeys
⋮----
"""Test adding resources and templates with custom keys."""
def test_add_resource_with_custom_key(self, temp_file: Path)
⋮----
"""Test adding a resource with a custom key different from its URI."""
⋮----
original_uri = "data://test/resource"
custom_key = "custom://resource/key"
# Create a function resource instead of file resource to avoid path issues
⋮----
# Use with_key to create a new resource with the custom key
resource_with_custom_key = resource.with_key(custom_key)
⋮----
# Resource should be accessible via custom key
⋮----
# But not via its original URI
⋮----
# The resource's internal URI remains unchanged
⋮----
def test_add_template_with_custom_key(self)
⋮----
"""Test adding a template with a custom key different from its URI template."""
⋮----
original_uri_template = "test://{id}"
custom_key = "custom://{id}/template"
⋮----
# Use with_key to create a new template with the custom key
template_with_custom_key = template.with_key(custom_key)
⋮----
# Template should be accessible via custom key
⋮----
# But not via its original URI template
⋮----
# The template's internal URI template remains unchanged
⋮----
async def test_get_resource_with_custom_key(self, temp_file: Path)
⋮----
"""Test that get_resource works with resources added with custom keys."""
⋮----
custom_key = "custom://resource/path"
⋮----
# Should be retrievable by the custom key
retrieved = await manager.get_resource(custom_key)
⋮----
# Should NOT be retrievable by the original URI
⋮----
async def test_get_resource_from_template_with_custom_key(self)
⋮----
"""Test that templates with custom keys can create resources."""
⋮----
original_template = "greet://{name}"
custom_key = "custom://greet/{name}"
⋮----
# Using a URI that matches the custom key pattern
resource = await manager.get_resource("custom://greet/world")
⋮----
# Shouldn't work with the original template pattern
⋮----
class TestResourceErrorHandling
⋮----
"""Test error handling in the ResourceManager."""
async def test_resource_error_passthrough(self)
⋮----
"""Test that ResourceErrors are passed through directly."""
⋮----
async def error_resource()
⋮----
"""Resource that raises a ResourceError."""
⋮----
async def test_template_resource_error_passthrough(self)
⋮----
"""Test that ResourceErrors from template-generated resources are passed through."""
⋮----
def error_template(param: str)
⋮----
"""Template that raises a ResourceError."""
⋮----
# The original error message should be included in the ValueError
⋮----
async def test_exception_converted_to_resource_error_with_details(self)
⋮----
"""Test that other exceptions are converted to ResourceError with details by default."""
⋮----
async def buggy_resource()
⋮----
"""Resource that raises a ValueError."""
⋮----
# The error message should include the original exception details
⋮----
async def test_exception_converted_to_masked_resource_error(self)
⋮----
"""Test that other exceptions are masked when enabled."""
manager = ResourceManager(mask_error_details=True)
⋮----
# The error message should not include the original exception details

================
File: tests/resources/test_resource_template.py
================
class TestResourceTemplate
⋮----
"""Test ResourceTemplate functionality."""
def test_template_creation(self)
⋮----
"""Test creating a template from a function."""
def my_func(key: str, value: int) -> dict
template = ResourceTemplate.from_function(
⋮----
assert template.mime_type == "text/plain"  # default
test_input = {"key": "test", "value": 42}
⋮----
def test_template_matches(self)
⋮----
"""Test matching URIs against a template."""
⋮----
# Valid match
params = template.matches("test://foo/123")
⋮----
# No match
⋮----
def test_template_matches_with_prefix(self)
⋮----
"""Test matching URIs against a template with a prefix."""
⋮----
params = template.matches("app+test://foo/123")
⋮----
def test_template_uri_validation(self)
⋮----
"""Test validation rule: URI template must have at least one parameter."""
def my_func() -> dict
⋮----
def test_template_uri_params_subset_of_function_params(self)
⋮----
"""Test validation rule: URI parameters must be a subset of function parameters."""
⋮----
# This should work - URI params are a subset of function params
⋮----
# This should fail - 'unknown' is not a function parameter
⋮----
def test_required_params_subset_of_uri_params(self)
⋮----
"""Test validation rule: Required function parameters must be in URI parameters."""
# Function with required parameters
⋮----
# This should work - required param is in URI
⋮----
# This should fail - required param is not in URI
⋮----
def test_multiple_required_params(self)
⋮----
"""Test validation with multiple required parameters."""
def multi_required(param1: str, param2: int, optional: str = "default") -> dict
# This works - all required params in URI
⋮----
# This fails - missing one required param
⋮----
async def test_create_resource(self)
⋮----
"""Test creating a resource from a template."""
⋮----
resource = await template.create_resource(
⋮----
content = await resource.read()
⋮----
data = json.loads(content)
⋮----
async def test_async_text_resource(self)
⋮----
"""Test creating a text resource from async function."""
async def greet(name: str) -> str
⋮----
async def test_async_binary_resource(self)
⋮----
"""Test creating a binary resource from async function."""
async def get_bytes(value: str) -> bytes
⋮----
async def test_basemodel_conversion(self)
⋮----
"""Test handling of BaseModel types."""
class MyModel(BaseModel)
⋮----
key: str
value: int
def get_data(key: str, value: int) -> MyModel
⋮----
async def test_custom_type_conversion(self)
⋮----
"""Test handling of custom types."""
class CustomData
⋮----
def __init__(self, value: str)
def __str__(self) -> str
def get_data(value: str) -> CustomData
⋮----
async def test_wildcard_param_can_create_resource(self)
⋮----
"""Test that wildcard parameters are valid."""
def identity(path: str) -> str
⋮----
async def test_wildcard_param_matches(self)
⋮----
def identify(path: str) -> str
⋮----
params = template.matches("test://src/path/to/test.py")
⋮----
async def test_multiple_wildcard_params(self)
⋮----
"""Test that multiple wildcard parameters are valid."""
def identity(path: str, path2: str) -> str
⋮----
params = template.matches("test://path/to/xyz/abc")
⋮----
async def test_wildcard_param_with_regular_param(self)
⋮----
"""Test that a wildcard parameter can be used with a regular parameter."""
def identity(prefix: str, path: str) -> str
⋮----
async def test_function_with_varargs_not_allowed(self)
⋮----
def func(x: int, *args: int) -> int
⋮----
async def test_function_with_varkwargs_ok(self)
⋮----
def func(x: int, **kwargs: int) -> int
⋮----
async def test_callable_object_as_template(self)
⋮----
"""Test that a callable object can be used as a template."""
class MyTemplate
⋮----
"""This is my template"""
def __call__(self, x: str) -> str
⋮----
"""ignore this"""
⋮----
class TestMatchUriTemplate
⋮----
"""Test match_uri_template function."""
⋮----
"""Test that match_uri_template uses the slash delimiter."""
uri_template = "test://a/{x}/b"
result = match_uri_template(uri=uri, uri_template=uri_template)
⋮----
"""Test matching URIs against a template with simple parameters."""
uri_template = "test://{x}/{y}"
⋮----
"""Test matching URIs against a template with parameters and literal segments."""
uri_template = "test://a/b/{x}/c/d/{y}"
⋮----
uri_template = "prefix+test://{x}/test/{y}"
⋮----
def test_match_uri_template_quoted_params(self)
⋮----
uri_template = "user://{name}/{email}"
quoted_name = quote("John Doe", safe="")
quoted_email = quote("john@example.com", safe="")
uri = f"user://{quoted_name}/{quoted_email}"
⋮----
uri_template = "test://a/{x*}/b"
⋮----
uri_template = "test://a/{x*}/b/{y*}"
⋮----
def test_match_uri_template_wildcard_and_literal_param(self)
⋮----
uri = "test://a/x/y/b"
uri_template = "test://a/{x*}/{y}"
⋮----
def test_match_consecutive_params(self)
⋮----
"""Test that consecutive parameters without a / are not matched."""
uri = "test://a/x/y"
uri_template = "test://a/{x}{y}"
⋮----
uri_template = "file://abc/{path*}.py"
⋮----
("resource://test_", None),  # Empty parameter not matched
("resource://test", None),  # Missing parameter delimiter
("resource://other_foo", None),  # Wrong prefix
("other://test_foo", None),  # Wrong scheme
⋮----
"""Test matching URIs where parameter is embedded within a word segment."""
uri_template = "resource://test_{x}"
⋮----
("resource://prefix__suffix", None),  # Empty parameter not matched
("resource://prefix_suffix", None),  # Missing parameter delimiter
("resource://other_foo_suffix", None),  # Wrong prefix
("resource://prefix_foo_other", None),  # Wrong suffix
⋮----
"""Test matching URIs where parameter has both prefix and suffix."""
uri_template = "resource://prefix_{x}_suffix"
⋮----
class TestContextHandling
⋮----
"""Test context handling in resource templates."""
def test_context_parameter_detection(self)
⋮----
"""Test that context parameters are properly detected in
        ResourceTemplate.from_function()."""
def template_with_context(x: int, ctx: Context) -> str
⋮----
def template_without_context(x: int) -> str
⋮----
def test_parameterized_context_parameter_detection(self)
⋮----
"""Test that parameterized context parameters are properly detected in
        ResourceTemplate.from_function()."""
⋮----
def test_parameterized_union_context_parameter_detection(self)
⋮----
"""Test that context parameters in a union are properly detected in
        ResourceTemplate.from_function()."""
def template_with_context(x: int, ctx: Context | None) -> str
⋮----
async def test_context_injection(self)
⋮----
"""Test that context is properly injected during resource creation."""
def resource_with_context(x: int, ctx: Context) -> str
⋮----
mcp = FastMCP()
context = Context(fastmcp=mcp)
⋮----
async def test_context_optional(self)
⋮----
"""Test that context is optional when creating resources."""
def resource_with_context(x: int, ctx: Context | None = None) -> str
⋮----
# Even for optional context, we need to provide a context

================
File: tests/resources/test_resources.py
================
class TestResourceValidation
⋮----
"""Test base Resource validation."""
def test_resource_uri_validation(self)
⋮----
"""Test URI validation."""
def dummy_func() -> str
# Valid URI
resource = FunctionResource(
⋮----
# Missing protocol
⋮----
# Missing host
⋮----
def test_resource_name_from_uri(self)
⋮----
"""Test name is extracted from URI if not provided."""
⋮----
def test_provided_name_takes_precedence_over_uri(self)
⋮----
"""Test that provided name takes precedence over URI."""
⋮----
# Explicit name takes precedence over URI
⋮----
def test_resource_mime_type(self)
⋮----
"""Test mime type handling."""
⋮----
# Default mime type
⋮----
# Custom mime type
⋮----
async def test_resource_read_abstract(self)
⋮----
"""Test that Resource.read() is abstract."""
class ConcreteResource(Resource)
⋮----
ConcreteResource(uri=AnyUrl("test://test"), name="test")  # type: ignore

================
File: tests/server/http/test_auth_setup.py
================
"""Tests for authentication setup in HTTP apps."""
⋮----
class TestSetupAuthMiddlewareAndRoutes
⋮----
"""Test setup_auth_middleware_and_routes with TokenVerifier providers."""
⋮----
@pytest.fixture
    def bearer_provider(self) -> BearerAuthProvider
⋮----
"""Create BearerAuthProvider for testing."""
key_pair = RSAKeyPair.generate()
⋮----
@pytest.fixture
    def in_memory_provider(self) -> InMemoryOAuthProvider
⋮----
"""Create InMemoryOAuthProvider for testing."""
⋮----
def test_setup_with_bearer_provider(self, bearer_provider: BearerAuthProvider)
⋮----
"""Test that setup works with BearerAuthProvider as TokenVerifier."""
⋮----
# Should return middleware list
⋮----
assert len(middleware) == 2  # AuthenticationMiddleware + AuthContextMiddleware
# First middleware should be AuthenticationMiddleware with BearerAuthBackend
auth_middleware = middleware[0]
⋮----
backend = auth_middleware.kwargs["backend"]
⋮----
assert backend.token_verifier is bearer_provider  # type: ignore[attr-defined]
# Should return auth routes
⋮----
assert len(auth_routes) > 0  # Should have OAuth routes
# Should return required scopes
⋮----
"""Test that setup works with InMemoryOAuthProvider as TokenVerifier."""
⋮----
# Backend should use the provider as token verifier
⋮----
assert backend.token_verifier is in_memory_provider  # type: ignore[attr-defined]
⋮----
"""Test that setup doesn't break the provider's functionality."""
# Setup should not modify the provider
original_issuer = bearer_provider.issuer
original_scopes = bearer_provider.required_scopes
⋮----
# Provider should be unchanged
⋮----
# Provider should still work as TokenVerifier
⋮----
class MockOAuthProvider
⋮----
"""Mock OAuth provider that implements TokenVerifier."""
def __init__(self, required_scopes=None, issuer_url="http://localhost:8000")
async def verify_token(self, token: str) -> AccessToken | None
⋮----
"""Mock verify_token implementation."""
⋮----
class TestSetupWithMockProvider
⋮----
"""Test setup function with mock provider."""
def test_setup_with_mock_token_verifier(self)
⋮----
"""Test that setup works with any TokenVerifier implementation."""
mock_provider = MockOAuthProvider(required_scopes=["mock-scope"])
⋮----
mock_provider  # type: ignore[arg-type]
⋮----
# Should work with any TokenVerifier
⋮----
assert backend.token_verifier is mock_provider  # type: ignore[attr-defined]
⋮----
async def test_setup_middleware_can_authenticate(self)
⋮----
"""Test that the setup middleware can actually authenticate requests."""
mock_provider = MockOAuthProvider()
middleware, _, _ = setup_auth_middleware_and_routes(mock_provider)  # type: ignore[arg-type]
# Extract the BearerAuthBackend
⋮----
# Test authentication with valid token
⋮----
scope = {
conn = HTTPConnection(scope)
result = await backend.authenticate(conn)  # type: ignore[attr-defined]
⋮----
# Test authentication with invalid token

================
File: tests/server/http/test_bearer_auth_backend.py
================
"""Tests for BearerAuthBackend integration with TokenVerifier."""
⋮----
class TestBearerAuthBackendTokenVerifierIntegration
⋮----
"""Test BearerAuthBackend works with TokenVerifier protocol."""
⋮----
@pytest.fixture
    def rsa_key_pair(self) -> RSAKeyPair
⋮----
"""Generate RSA key pair for testing."""
⋮----
@pytest.fixture
    def bearer_provider(self, rsa_key_pair: RSAKeyPair) -> BearerAuthProvider
⋮----
"""Create BearerAuthProvider for testing."""
⋮----
@pytest.fixture
    def valid_token(self, rsa_key_pair: RSAKeyPair) -> str
⋮----
"""Create a valid test token."""
⋮----
"""Test that BearerAuthBackend constructor accepts TokenVerifier."""
# This should not raise an error
backend = BearerAuthBackend(bearer_provider)
assert backend.token_verifier is bearer_provider  # type: ignore[attr-defined]
⋮----
"""Test BearerAuthBackend authentication with valid token."""
⋮----
# Create mock HTTPConnection with Authorization header
scope = {
conn = HTTPConnection(scope)
result = await backend.authenticate(conn)
⋮----
"""Test BearerAuthBackend authentication with invalid token."""
⋮----
# Create mock HTTPConnection with invalid Authorization header
⋮----
"""Test BearerAuthBackend authentication with no Authorization header."""
⋮----
# Create mock HTTPConnection without Authorization header
⋮----
"""Test BearerAuthBackend authentication with non-Bearer token."""
⋮----
# Create mock HTTPConnection with Basic auth header
⋮----
class MockTokenVerifier
⋮----
"""Mock TokenVerifier for testing backend integration."""
def __init__(self, return_value: AccessToken | None = None)
async def verify_token(self, token: str) -> AccessToken | None
⋮----
"""Mock verify_token method."""
⋮----
class TestBearerAuthBackendWithMockVerifier
⋮----
"""Test BearerAuthBackend with mock TokenVerifier."""
async def test_backend_calls_verify_token_method(self)
⋮----
"""Test that BearerAuthBackend calls verify_token on the verifier."""
mock_access_token = AccessToken(
mock_verifier = MockTokenVerifier(return_value=mock_access_token)
backend = BearerAuthBackend(mock_verifier)  # type: ignore[arg-type]
⋮----
# Should have called verify_token with the token
⋮----
# Should return authentication result
⋮----
async def test_backend_handles_verify_token_none_result(self)
⋮----
"""Test that BearerAuthBackend handles None result from verify_token."""
mock_verifier = MockTokenVerifier(return_value=None)
⋮----
# Should have called verify_token
⋮----
# Should return None for authentication failure

================
File: tests/server/http/test_custom_routes.py
================
class TestCustomRoutes
⋮----
@pytest.fixture
    def server_with_custom_route(self)
⋮----
"""Create a FastMCP server with a custom route."""
server = FastMCP()
⋮----
@server.custom_route("/custom-route", methods=["GET"])
        async def custom_route(request: Request)
⋮----
def test_custom_routes_via_server_http_app(self, server_with_custom_route)
⋮----
"""Test that custom routes are included when using server.http_app()."""
# Get the app via server.http_app()
app = server_with_custom_route.http_app()
# Verify that the custom route is included
custom_route_found = False
⋮----
custom_route_found = True
⋮----
"""Test that custom routes are included when using create_streamable_http_app directly."""
# Create the app by calling the constructor function directly
app = create_streamable_http_app(
⋮----
def test_custom_routes_via_sse_app_direct(self, server_with_custom_route)
⋮----
"""Test that custom routes are included when using create_sse_app directly."""
⋮----
app = create_sse_app(
⋮----
"""Test that multiple custom routes are included in both methods."""
⋮----
custom_paths = ["/route1", "/route2", "/route3"]
# Add multiple custom routes
⋮----
@server.custom_route(path, methods=["GET"])
            async def custom_route(request: Request)
# Test with server.http_app()
app1 = server.http_app()
# Test with direct constructor call
app2 = create_streamable_http_app(server=server, streamable_http_path="/api")
# Check all routes are in both apps
⋮----
# Check in app1
route_in_app1 = any(
⋮----
# Check in app2
route_in_app2 = any(

================
File: tests/server/http/test_http_dependencies.py
================
def fastmcp_server()
⋮----
server = FastMCP()
# Add a tool
⋮----
@server.tool
    def get_headers_tool() -> dict[str, str]
⋮----
"""Get the HTTP headers from the request."""
request = get_http_request()
⋮----
@server.resource(uri="request://headers")
    async def get_headers_resource() -> dict[str, str]
# Add a prompt
⋮----
@server.prompt
    def get_headers_prompt() -> str
⋮----
def run_server(host: str, port: int, **kwargs) -> None
⋮----
@pytest.fixture(autouse=True, scope="module")
def shttp_server() -> Generator[str, None, None]
⋮----
@pytest.fixture(autouse=True, scope="module")
def sse_server() -> Generator[str, None, None]
async def test_http_headers_resource_shttp(shttp_server: str)
⋮----
"""Test getting HTTP headers from the server."""
⋮----
raw_result = await client.read_resource("request://headers")
json_result = json.loads(raw_result[0].text)  # type: ignore[attr-defined]
⋮----
async def test_http_headers_resource_sse(sse_server: str)
async def test_http_headers_tool_shttp(shttp_server: str)
⋮----
result = await client.call_tool("get_headers_tool")
⋮----
async def test_http_headers_tool_sse(sse_server: str)
async def test_http_headers_prompt_shttp(shttp_server: str)
⋮----
result = await client.get_prompt("get_headers_prompt")
json_result = json.loads(result.messages[0].content.text)  # type: ignore[attr-defined]
⋮----
async def test_http_headers_prompt_sse(sse_server: str)

================
File: tests/server/http/test_http_middleware.py
================
"""Tests for middleware in HTTP apps."""
⋮----
class HeaderMiddleware(BaseHTTPMiddleware)
⋮----
"""Simple middleware that adds a custom header to responses."""
def __init__(self, app: ASGIApp, header_name: str, header_value: str)
async def dispatch(self, request: Request, call_next: Callable)
⋮----
response = await call_next(request)
⋮----
class RequestModifierMiddleware(BaseHTTPMiddleware)
⋮----
"""Middleware that adds a value to request state."""
def __init__(self, app: ASGIApp, key: str, value: Any)
⋮----
async def endpoint_handler(request: Request)
⋮----
"""Endpoint that returns request state or headers."""
⋮----
async def test_sse_app_with_custom_middleware()
⋮----
"""Test that custom middleware works with SSE app."""
server = FastMCP(name="TestServer")
# Create custom middleware
custom_middleware = [
# Add a test route to server's additional routes
routes: list[BaseRoute] = [Route("/test", endpoint_handler)]
⋮----
# Create the app with custom middleware
app = server.http_app(transport="sse", middleware=custom_middleware)
# Create a test client
transport = ASGITransport(app=app)
⋮----
response = await client.get("/test")
# Verify middleware was applied
⋮----
async def test_streamable_http_app_with_custom_middleware()
⋮----
"""Test that custom middleware works with StreamableHTTP app."""
⋮----
app = server.http_app(transport="http", middleware=custom_middleware)
⋮----
async def test_create_sse_app_with_custom_middleware()
⋮----
"""Test that custom middleware works with create_sse_app function."""
⋮----
# Add a test route
additional_routes: list[BaseRoute] = [Route("/test", endpoint_handler)]
⋮----
app = create_sse_app(
⋮----
data = response.json()
⋮----
async def test_create_streamable_http_app_with_custom_middleware()
⋮----
"""Test that custom middleware works with create_streamable_http_app function."""
⋮----
app = create_streamable_http_app(
⋮----
async def test_multiple_middleware_ordering()
⋮----
"""Test that multiple middleware are applied in the correct order."""
⋮----
# Create multiple middleware
⋮----
# Verify both middleware were applied

================
File: tests/server/middleware/test_error_handling.py
================
"""Tests for error handling middleware."""
⋮----
@pytest.fixture
def mock_context()
⋮----
"""Create a mock middleware context."""
context = MagicMock(spec=MiddlewareContext)
⋮----
@pytest.fixture
def mock_call_next()
⋮----
"""Create a mock call_next function."""
⋮----
class TestErrorHandlingMiddleware
⋮----
"""Test error handling middleware functionality."""
def test_init_default(self)
⋮----
"""Test default initialization."""
middleware = ErrorHandlingMiddleware()
⋮----
def test_init_custom(self)
⋮----
"""Test custom initialization."""
logger = logging.getLogger("custom")
callback = MagicMock()
middleware = ErrorHandlingMiddleware(
⋮----
def test_log_error_basic(self, mock_context, caplog)
⋮----
"""Test basic error logging."""
⋮----
error = ValueError("test error")
⋮----
def test_log_error_with_traceback(self, mock_context, caplog)
⋮----
"""Test error logging with traceback."""
middleware = ErrorHandlingMiddleware(include_traceback=True)
⋮----
# The traceback is added to the log message
⋮----
def test_log_error_with_callback(self, mock_context)
⋮----
"""Test error logging with callback."""
⋮----
middleware = ErrorHandlingMiddleware(error_callback=callback)
⋮----
def test_log_error_callback_exception(self, mock_context, caplog)
⋮----
"""Test error logging when callback raises exception."""
callback = MagicMock(side_effect=RuntimeError("callback error"))
⋮----
def test_transform_error_mcp_error(self)
⋮----
"""Test that MCP errors are not transformed."""
⋮----
error = McpError(ErrorData(code=-32001, message="test error"))
result = middleware._transform_error(error)
⋮----
def test_transform_error_disabled(self)
⋮----
"""Test error transformation when disabled."""
middleware = ErrorHandlingMiddleware(transform_errors=False)
⋮----
def test_transform_error_value_error(self)
⋮----
"""Test transforming ValueError."""
⋮----
def test_transform_error_file_not_found(self)
⋮----
"""Test transforming FileNotFoundError."""
⋮----
error = FileNotFoundError("test error")
⋮----
def test_transform_error_permission_error(self)
⋮----
"""Test transforming PermissionError."""
⋮----
error = PermissionError("test error")
⋮----
def test_transform_error_timeout_error(self)
⋮----
"""Test transforming TimeoutError."""
⋮----
error = TimeoutError("test error")
⋮----
def test_transform_error_generic(self)
⋮----
"""Test transforming generic error."""
⋮----
error = RuntimeError("test error")
⋮----
async def test_on_message_success(self, mock_context, mock_call_next)
⋮----
"""Test successful message handling."""
⋮----
result = await middleware.on_message(mock_context, mock_call_next)
⋮----
async def test_on_message_error_transform(self, mock_context, caplog)
⋮----
"""Test error handling with transformation."""
⋮----
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
⋮----
def test_get_error_stats(self, mock_context)
⋮----
"""Test getting error statistics."""
⋮----
error1 = ValueError("error1")
error2 = ValueError("error2")
error3 = RuntimeError("error3")
⋮----
stats = middleware.get_error_stats()
⋮----
class TestRetryMiddleware
⋮----
"""Test retry middleware functionality."""
⋮----
middleware = RetryMiddleware()
⋮----
middleware = RetryMiddleware(
⋮----
def test_should_retry_true(self)
⋮----
"""Test retry decision for retryable errors."""
⋮----
def test_should_retry_false(self)
⋮----
"""Test retry decision for non-retryable errors."""
⋮----
def test_calculate_delay(self)
⋮----
"""Test delay calculation."""
⋮----
assert middleware._calculate_delay(4) == 10.0  # capped at max_delay
async def test_on_request_success_first_try(self, mock_context, mock_call_next)
⋮----
"""Test successful request on first try."""
⋮----
result = await middleware.on_request(mock_context, mock_call_next)
⋮----
async def test_on_request_success_after_retries(self, mock_context, caplog)
⋮----
"""Test successful request after retries."""
middleware = RetryMiddleware(base_delay=0.01)  # Fast retry for testing
# Fail first two attempts, succeed on third
mock_call_next = AsyncMock(
⋮----
async def test_on_request_max_retries_exceeded(self, mock_context, caplog)
⋮----
"""Test request failing after max retries."""
middleware = RetryMiddleware(max_retries=2, base_delay=0.01)
# Fail all attempts
mock_call_next = AsyncMock(side_effect=ConnectionError("connection failed"))
⋮----
assert mock_call_next.call_count == 3  # initial + 2 retries
⋮----
async def test_on_request_non_retryable_error(self, mock_context)
⋮----
"""Test non-retryable error is not retried."""
⋮----
mock_call_next = AsyncMock(side_effect=ValueError("non-retryable"))
⋮----
assert mock_call_next.call_count == 1  # No retries
⋮----
@pytest.fixture
def error_handling_server()
⋮----
"""Create a FastMCP server specifically for error handling middleware tests."""
⋮----
mcp = FastMCP("ErrorHandlingTestServer")
⋮----
@mcp.tool
    def reliable_operation(data: str) -> str
⋮----
"""A reliable operation that always succeeds."""
⋮----
@mcp.tool
    def failing_operation(error_type: str = "value") -> str
⋮----
"""An operation that fails with different error types."""
⋮----
@mcp.tool
    def intermittent_operation(fail_rate: float = 0.5) -> str
⋮----
"""An operation that fails intermittently."""
⋮----
@mcp.tool
    def retryable_operation(attempt_count: int = 0) -> str
⋮----
"""An operation that succeeds after a few attempts."""
# This is a simple way to simulate retry behavior
# In a real scenario, you might use external state
⋮----
class TestErrorHandlingMiddlewareIntegration
⋮----
"""Integration tests for error handling middleware with real FastMCP server."""
⋮----
"""Test that error handling middleware logs real errors from tools."""
⋮----
# Test different types of errors
⋮----
log_text = caplog.text
# Should have error logs for both failures
⋮----
# Should have captured both error instances
error_count = log_text.count("Error in tools/call:")
⋮----
"""Test that error handling middleware accurately tracks error statistics."""
⋮----
error_middleware = ErrorHandlingMiddleware()
⋮----
# Generate different types of errors
⋮----
# Try some intermittent operations (some may succeed)
⋮----
pass  # Expected failures
# Check error statistics
stats = error_middleware.get_error_stats()
# Should have tracked the ToolError wrapper
⋮----
assert stats["ToolError:tools/call"] >= 5  # At least the 5 deliberate failures
⋮----
"""Test error handling middleware with mix of successful and failed operations."""
⋮----
# Successful operation (should not generate error logs)
⋮----
# Failed operation (should generate error log)
⋮----
# Another successful operation
⋮----
# Should only have one error log (for the failed operation)
⋮----
"""Test error handling middleware with custom error callback."""
⋮----
captured_errors = []
def error_callback(error, context)
⋮----
# Generate some errors
⋮----
# Check that callback was called
⋮----
"""Test error transformation functionality."""
⋮----
# All errors should still be raised, but potentially transformed
⋮----
# Error should still exist (may be wrapped by FastMCP)
⋮----
class TestRetryMiddlewareIntegration
⋮----
"""Integration tests for retry middleware with real FastMCP server."""
⋮----
"""Test retry middleware with operations that have transient failures."""
⋮----
# Configure retry middleware to retry connection errors
⋮----
base_delay=0.01,  # Very short delay for testing
⋮----
# This operation fails intermittently - try several times
success_count = 0
⋮----
pass  # Some failures expected even with retries
# Should have some retry log messages
# Note: Retry logs might not appear if the underlying errors are wrapped by FastMCP
# The key is that some operations should succeed due to retries
⋮----
"""Test that retry middleware doesn't retry non-retryable errors."""
⋮----
# Configure retry middleware for connection errors only
⋮----
# Value errors should not be retried
⋮----
# Should fail immediately without retries
⋮----
"""Test error handling and retry middleware working together."""
⋮----
# Add both middleware
⋮----
# Try intermittent operation
⋮----
pass  # May still fail even with retries
# Try permanent failure
⋮----
# Should have error logs from error handling middleware

================
File: tests/server/middleware/test_logging.py
================
"""Tests for logging middleware."""
⋮----
@pytest.fixture
def mock_context()
⋮----
"""Create a mock middleware context."""
context = MagicMock(spec=MiddlewareContext)
⋮----
@pytest.fixture
def mock_call_next()
⋮----
"""Create a mock call_next function."""
⋮----
class TestLoggingMiddleware
⋮----
"""Test logging middleware functionality."""
def test_init_default(self)
⋮----
"""Test default initialization."""
middleware = LoggingMiddleware()
⋮----
def test_init_custom(self)
⋮----
"""Test custom initialization."""
logger = logging.getLogger("custom")
middleware = LoggingMiddleware(
⋮----
def test_format_message_without_payloads(self, mock_context)
⋮----
"""Test message formatting without payloads."""
⋮----
formatted = middleware._format_message(mock_context)
⋮----
def test_format_message_with_payloads(self, mock_context)
⋮----
"""Test message formatting with payloads."""
middleware = LoggingMiddleware(include_payloads=True)
⋮----
def test_format_message_long_payload(self, mock_context)
⋮----
"""Test message formatting with long payload truncation."""
middleware = LoggingMiddleware(include_payloads=True, max_payload_length=10)
⋮----
async def test_on_message_success(self, mock_context, mock_call_next, caplog)
⋮----
"""Test logging successful messages."""
⋮----
result = await middleware.on_message(mock_context, mock_call_next)
⋮----
async def test_on_message_failure(self, mock_context, caplog)
⋮----
"""Test logging failed messages."""
⋮----
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
⋮----
class TestStructuredLoggingMiddleware
⋮----
"""Test structured logging middleware functionality."""
⋮----
middleware = StructuredLoggingMiddleware()
⋮----
def test_create_log_entry_basic(self, mock_context)
⋮----
"""Test creating basic log entry."""
⋮----
entry = middleware._create_log_entry(mock_context, "test_event")
⋮----
def test_create_log_entry_with_payload(self, mock_context)
⋮----
"""Test creating log entry with payload."""
middleware = StructuredLoggingMiddleware(include_payloads=True)
⋮----
def test_create_log_entry_with_extra_fields(self, mock_context)
⋮----
"""Test creating log entry with extra fields."""
⋮----
entry = middleware._create_log_entry(
⋮----
"""Test structured logging of successful messages."""
⋮----
# Check that we have structured JSON logs
log_lines = [record.message for record in caplog.records]
assert len(log_lines) == 2  # start and success entries
start_entry = json.loads(log_lines[0])
⋮----
success_entry = json.loads(log_lines[1])
⋮----
"""Test structured logging of failed messages."""
⋮----
assert len(log_lines) == 2  # start and error entries
⋮----
error_entry = json.loads(log_lines[1])
⋮----
@pytest.fixture
def logging_server()
⋮----
"""Create a FastMCP server specifically for logging middleware tests."""
⋮----
mcp = FastMCP("LoggingTestServer")
⋮----
@mcp.tool
    def simple_operation(data: str) -> str
⋮----
"""A simple operation for testing logging."""
⋮----
@mcp.tool
    def complex_operation(items: list[str], mode: str = "default") -> dict
⋮----
"""A complex operation with structured data."""
⋮----
@mcp.tool
    def operation_with_error(should_fail: bool = False) -> str
⋮----
"""An operation that can be made to fail."""
⋮----
@mcp.resource("log://test")
    def test_resource() -> str
⋮----
"""A test resource for logging."""
⋮----
@mcp.prompt
    def test_prompt() -> str
⋮----
"""A test prompt for logging."""
⋮----
class TestLoggingMiddlewareIntegration
⋮----
"""Integration tests for logging middleware with real FastMCP server."""
⋮----
"""Test that logging middleware captures successful operations."""
⋮----
log_text = caplog.text
# Should have processing and completion logs for both operations
⋮----
# Should have captured both tool calls
processing_count = log_text.count("Processing message:")
completion_count = log_text.count("Completed message:")
⋮----
async def test_logging_middleware_logs_failures(self, logging_server, caplog)
⋮----
"""Test that logging middleware captures failed operations."""
⋮----
# This should fail and be logged
⋮----
# Should have processing and failure logs
⋮----
async def test_logging_middleware_with_payloads(self, logging_server, caplog)
⋮----
"""Test logging middleware when configured to include payloads."""
⋮----
# Should include payload information
⋮----
"""Test that structured logging middleware produces parseable JSON logs."""
⋮----
# Extract JSON log entries
log_lines = [
assert len(log_lines) >= 2  # Should have start and success entries
# Each log line should be valid JSON
⋮----
log_entry = json.loads(line)
⋮----
"""Test structured logging of errors with JSON format."""
⋮----
# Should have start and error entries
⋮----
# Find the error entry
error_entries = []
⋮----
error_entry = error_entries[0]
⋮----
"""Test logging middleware with various MCP operations."""
⋮----
# Test different operation types
⋮----
# Should have logs for all different operation types
# Note: Different operations may have different method names
⋮----
# Should have processed all 4 operations
⋮----
async def test_logging_middleware_custom_configuration(self, logging_server)
⋮----
"""Test logging middleware with custom logger configuration."""
⋮----
# Create custom logger
log_buffer = io.StringIO()
handler = logging.StreamHandler(log_buffer)
custom_logger = logging.getLogger("custom_logging_test")
⋮----
# Check that our custom logger captured the logs
log_output = log_buffer.getvalue()

================
File: tests/server/middleware/test_middleware.py
================
@dataclass
class Recording
⋮----
# the hook is the name of the hook that was called, e.g. "on_list_tools"
hook: str
context: MiddlewareContext
result: mcp.types.ServerResult | None
class RecordingMiddleware(Middleware)
⋮----
"""A middleware that automatically records all method calls."""
def __init__(self, name: str | None = None)
def __getattribute__(self, name: str) -> Callable
⋮----
"""Dynamically create recording methods for any on_* method."""
⋮----
result = await call_next(context)
⋮----
"""
        Get all recorded calls for a specific method or hook.
        Args:
            method: The method to filter by (e.g. "tools/list")
            hook: The hook to filter by (e.g. "on_list_tools")
        Returns:
            A list of recorded calls.
        """
calls = []
⋮----
"""Assert that a hook was called a specific number of times."""
⋮----
times = 1
calls = self.get_calls(hook=hook, method=method)
actual_times = len(calls)
identifier = dict(hook=hook, method=method)
⋮----
def assert_not_called(self, hook: str | None = None, method: str | None = None)
⋮----
"""Assert that a hook was not called."""
⋮----
def reset(self)
⋮----
"""Clear all recorded calls."""
⋮----
@pytest.fixture
def recording_middleware()
⋮----
"""Fixture that provides a recording middleware instance."""
middleware = RecordingMiddleware(name="recording_middleware")
⋮----
@pytest.fixture
def mcp_server(recording_middleware)
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    def add(a: int, b: int) -> int
⋮----
@mcp.resource("resource://test")
    def test_resource() -> str
⋮----
@mcp.resource("resource://test-template/{x}")
    def test_resource_with_path(x: int) -> str
⋮----
@mcp.prompt
    def test_prompt(x: str) -> str
⋮----
@mcp.tool
    async def progress_tool(context: Context) -> None
⋮----
@mcp.tool
    async def log_tool(context: Context) -> None
⋮----
@mcp.tool
    async def sample_tool(context: Context) -> None
⋮----
# Register progress handler
⋮----
class TestMiddlewareHooks
class TestNestedMiddlewareHooks
⋮----
@pytest.fixture
@staticmethod
    def nested_middleware()
⋮----
@pytest.fixture
    def nested_mcp_server(self, nested_middleware: RecordingMiddleware)
⋮----
mcp = FastMCP(name="Nested MCP")
⋮----
@mcp.tool
        def add(a: int, b: int) -> int
⋮----
@mcp.resource("resource://test")
        def test_resource() -> str
⋮----
@mcp.resource("resource://test-template/{x}")
        def test_resource_with_path(x: int) -> str
⋮----
@mcp.prompt
        def test_prompt(x: str) -> str
⋮----
@mcp.tool
        async def progress_tool(context: Context) -> None
⋮----
@mcp.tool
        async def log_tool(context: Context) -> None
⋮----
@mcp.tool
        async def sample_tool(context: Context) -> None
⋮----
class TestProxyServer
⋮----
# proxy server will have its tools listed as well as called in order to
# run the `should_enable_component` hook prior to the call.
proxy_server = FastMCP.as_proxy(mcp_server, name="Proxy Server")

================
File: tests/server/middleware/test_rate_limiting.py
================
"""Tests for rate limiting middleware."""
⋮----
@pytest.fixture
def mock_context()
⋮----
"""Create a mock middleware context."""
context = MagicMock(spec=MiddlewareContext)
⋮----
@pytest.fixture
def mock_call_next()
⋮----
"""Create a mock call_next function."""
⋮----
class TestTokenBucketRateLimiter
⋮----
"""Test token bucket rate limiter."""
def test_init(self)
⋮----
"""Test initialization."""
limiter = TokenBucketRateLimiter(capacity=10, refill_rate=5.0)
⋮----
async def test_consume_success(self)
⋮----
"""Test successful token consumption."""
⋮----
# Should be able to consume tokens initially
⋮----
async def test_consume_failure(self)
⋮----
"""Test failed token consumption."""
limiter = TokenBucketRateLimiter(capacity=5, refill_rate=1.0)
# Consume all tokens
⋮----
# Should fail to consume more
⋮----
async def test_refill(self)
⋮----
"""Test token refill over time."""
limiter = TokenBucketRateLimiter(
⋮----
)  # 10 tokens per second
⋮----
# Wait for refill (0.2 seconds = 2 tokens at 10/sec)
⋮----
class TestSlidingWindowRateLimiter
⋮----
"""Test sliding window rate limiter."""
⋮----
limiter = SlidingWindowRateLimiter(max_requests=10, window_seconds=60)
⋮----
async def test_is_allowed_success(self)
⋮----
"""Test allowing requests within limit."""
limiter = SlidingWindowRateLimiter(max_requests=3, window_seconds=60)
# Should allow requests up to the limit
⋮----
async def test_is_allowed_failure(self)
⋮----
"""Test rejecting requests over limit."""
limiter = SlidingWindowRateLimiter(max_requests=2, window_seconds=60)
# Should allow up to limit
⋮----
# Should reject over limit
⋮----
async def test_sliding_window(self)
⋮----
"""Test sliding window behavior."""
limiter = SlidingWindowRateLimiter(max_requests=2, window_seconds=1)
# Use up requests
⋮----
# Wait for window to pass
⋮----
# Should be able to make requests again
⋮----
class TestRateLimitingMiddleware
⋮----
"""Test rate limiting middleware."""
def test_init_default(self)
⋮----
"""Test default initialization."""
middleware = RateLimitingMiddleware()
⋮----
def test_init_custom(self)
⋮----
"""Test custom initialization."""
def get_client_id(ctx)
middleware = RateLimitingMiddleware(
⋮----
def test_get_client_identifier_default(self, mock_context)
⋮----
"""Test default client identifier."""
⋮----
def test_get_client_identifier_custom(self, mock_context)
⋮----
"""Test custom client identifier."""
⋮----
middleware = RateLimitingMiddleware(get_client_id=get_client_id)
⋮----
async def test_on_request_success(self, mock_context, mock_call_next)
⋮----
"""Test successful request within rate limit."""
middleware = RateLimitingMiddleware(max_requests_per_second=100.0)  # High limit
result = await middleware.on_request(mock_context, mock_call_next)
⋮----
async def test_on_request_rate_limited(self, mock_context, mock_call_next)
⋮----
"""Test request rejection due to rate limiting."""
⋮----
# First request should succeed
⋮----
# Second request should be rate limited
⋮----
async def test_global_rate_limiting(self, mock_context, mock_call_next)
⋮----
"""Test global rate limiting."""
⋮----
class TestSlidingWindowRateLimitingMiddleware
⋮----
"""Test sliding window rate limiting middleware."""
⋮----
middleware = SlidingWindowRateLimitingMiddleware(max_requests=100)
⋮----
middleware = SlidingWindowRateLimitingMiddleware(
⋮----
assert middleware.window_seconds == 300  # 5 minutes
⋮----
middleware = SlidingWindowRateLimitingMiddleware(max_requests=1)
⋮----
class TestRateLimitError
⋮----
"""Test rate limit error."""
⋮----
error = RateLimitError()
⋮----
error = RateLimitError("Custom message")
⋮----
@pytest.fixture
def rate_limit_server()
⋮----
"""Create a FastMCP server specifically for rate limiting tests."""
mcp = FastMCP("RateLimitTestServer")
⋮----
@mcp.tool
    def quick_action(message: str) -> str
⋮----
"""A quick action for testing rate limits."""
⋮----
@mcp.tool
    def batch_process(items: list[str]) -> str
⋮----
"""Process multiple items."""
⋮----
@mcp.tool
    def heavy_computation() -> str
⋮----
"""A heavy computation that might need rate limiting."""
# Simulate some work
⋮----
time.sleep(0.01)  # Very short delay
⋮----
class TestRateLimitingMiddlewareIntegration
⋮----
"""Integration tests for rate limiting middleware with real FastMCP server."""
async def test_rate_limiting_allows_normal_usage(self, rate_limit_server)
⋮----
"""Test that normal usage patterns are allowed through rate limiting."""
# Generous rate limit
⋮----
# Normal usage should be fine
⋮----
result = await client.call_tool(
⋮----
async def test_rate_limiting_blocks_rapid_requests(self, rate_limit_server)
⋮----
"""Test that rate limiting blocks rapid successive requests."""
# Very restrictive rate limit (accounting for extra list_tools calls per tool call)
⋮----
# First few should succeed (within burst capacity)
⋮----
# Next should be rate limited
⋮----
async def test_rate_limiting_with_concurrent_requests(self, rate_limit_server)
⋮----
"""Test rate limiting behavior with concurrent requests."""
⋮----
# Fire off many concurrent requests
tasks = []
⋮----
task = asyncio.create_task(
⋮----
# Gather results, allowing exceptions
results = await asyncio.gather(*tasks, return_exceptions=True)
# With extra list_tools calls, the exact behavior is unpredictable
# Just verify that rate limiting is working (not all succeed)
successes = [r for r in results if not isinstance(r, Exception)]
failures = [r for r in results if isinstance(r, Exception)]
total_results = len(successes) + len(failures)
⋮----
# With the unpredictable list_tools calls, we just verify that the system
# is working (all requests should either succeed or fail with some exception)
⋮----
async def test_sliding_window_rate_limiting(self, rate_limit_server)
⋮----
"""Test sliding window rate limiting implementation."""
⋮----
max_requests=5,  # Accounting for extra list_tools calls
window_minutes=1,  # 1 minute window
⋮----
# Should allow up to the limit
⋮----
# Fourth should be blocked
⋮----
async def test_rate_limiting_with_different_operations(self, rate_limit_server)
⋮----
"""Test that rate limiting applies to all types of operations."""
⋮----
# Mix different operations
⋮----
# Should be rate limited regardless of operation type
⋮----
async def test_custom_client_identification(self, rate_limit_server)
⋮----
"""Test rate limiting with custom client identification."""
def get_client_id(context)
⋮----
# In a real scenario, this might extract from headers or context
⋮----
max_requests_per_second=6.0,  # Accounting for extra list_tools calls
⋮----
# First request should succeed
⋮----
# Second should be rate limited for this specific client
⋮----
async def test_global_rate_limiting(self, rate_limit_server)
⋮----
"""Test global rate limiting across all clients."""
⋮----
global_limit=True,  # Accounting for extra list_tools calls
⋮----
# Use up the global capacity
⋮----
# Should be globally rate limited
⋮----
async def test_rate_limiting_recovery_over_time(self, rate_limit_server)
⋮----
"""Test that rate limiting allows requests again after time passes."""
⋮----
max_requests_per_second=10.0,  # 10 per second = 1 every 100ms
⋮----
# Use up capacity
⋮----
# Should be rate limited immediately
⋮----
# Wait for token bucket to refill (150ms should be enough for ~1.5 tokens)
⋮----
# Should be able to make another request
result = await client.call_tool("quick_action", {"message": "after_wait"})

================
File: tests/server/middleware/test_timing.py
================
"""Tests for timing middleware."""
⋮----
@pytest.fixture
def mock_context()
⋮----
"""Create a mock middleware context."""
context = MagicMock(spec=MiddlewareContext)
⋮----
@pytest.fixture
def mock_call_next()
⋮----
"""Create a mock call_next function."""
⋮----
class TestTimingMiddleware
⋮----
"""Test timing middleware functionality."""
def test_init_default(self)
⋮----
"""Test default initialization."""
middleware = TimingMiddleware()
⋮----
def test_init_custom(self)
⋮----
"""Test custom initialization."""
logger = logging.getLogger("custom")
middleware = TimingMiddleware(logger=logger, log_level=logging.DEBUG)
⋮----
async def test_on_request_success(self, mock_context, mock_call_next, caplog)
⋮----
"""Test timing successful requests."""
⋮----
result = await middleware.on_request(mock_context, mock_call_next)
⋮----
async def test_on_request_failure(self, mock_context, caplog)
⋮----
"""Test timing failed requests."""
⋮----
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
⋮----
class TestDetailedTimingMiddleware
⋮----
"""Test detailed timing middleware functionality."""
⋮----
middleware = DetailedTimingMiddleware()
⋮----
async def test_on_call_tool(self, caplog)
⋮----
"""Test timing tool calls."""
⋮----
context = MagicMock()
⋮----
mock_call_next = AsyncMock(return_value="tool_result")
⋮----
result = await middleware.on_call_tool(context, mock_call_next)
⋮----
async def test_on_read_resource(self, caplog)
⋮----
"""Test timing resource reads."""
⋮----
mock_call_next = AsyncMock(return_value="resource_result")
⋮----
result = await middleware.on_read_resource(context, mock_call_next)
⋮----
async def test_on_get_prompt(self, caplog)
⋮----
"""Test timing prompt retrieval."""
⋮----
mock_call_next = AsyncMock(return_value="prompt_result")
⋮----
result = await middleware.on_get_prompt(context, mock_call_next)
⋮----
async def test_on_list_tools(self, caplog)
⋮----
"""Test timing tool listing."""
⋮----
mock_call_next = AsyncMock(return_value="tools_result")
⋮----
result = await middleware.on_list_tools(context, mock_call_next)
⋮----
async def test_operation_failure(self, caplog)
⋮----
"""Test timing failed operations."""
⋮----
mock_call_next = AsyncMock(side_effect=RuntimeError("operation failed"))
⋮----
@pytest.fixture
def timing_server()
⋮----
"""Create a FastMCP server specifically for timing middleware tests."""
mcp = FastMCP("TimingTestServer")
⋮----
@mcp.tool
    def instant_task() -> str
⋮----
"""A task that completes instantly."""
⋮----
@mcp.tool
    def short_task() -> str
⋮----
"""A task that takes 0.1 seconds."""
⋮----
@mcp.tool
    def medium_task() -> str
⋮----
"""A task that takes 0.15 seconds."""
⋮----
@mcp.tool
    def failing_task() -> str
⋮----
"""A task that always fails."""
⋮----
@mcp.resource("timer://test")
    def test_resource() -> str
⋮----
"""A resource that takes time to read."""
⋮----
@mcp.prompt
    def test_prompt() -> str
⋮----
"""A prompt that takes time to generate."""
⋮----
class TestTimingMiddlewareIntegration
⋮----
"""Integration tests for timing middleware with real FastMCP server."""
⋮----
"""Test that timing middleware accurately measures tool execution times."""
⋮----
# Test instant task
⋮----
# Test short task (0.1s)
⋮----
# Test medium task (0.15s)
⋮----
log_text = caplog.text
# Should have timing logs for all three calls (plus any extra list_tools calls)
timing_logs = [
⋮----
)  # At least 3 tool calls, may have additional list_tools calls
# Verify that longer tasks show longer timing (roughly)
⋮----
async def test_timing_middleware_handles_failures(self, timing_server, caplog)
⋮----
"""Test that timing middleware measures time even for failed operations."""
⋮----
# This should fail but still be timed
⋮----
# Should log the failure with timing
⋮----
"""Test that detailed timing middleware provides operation-specific timing."""
⋮----
# Test tool call
⋮----
# Test resource read
⋮----
# Test prompt
⋮----
# Test listing operations
⋮----
# Should have specific timing logs for each operation type
⋮----
async def test_timing_middleware_concurrent_operations(self, timing_server, caplog)
⋮----
"""Test timing middleware with concurrent operations."""
⋮----
# Run multiple operations concurrently
tasks = [
⋮----
# Should have timing logs for all concurrent operations (including extra list_tools calls)
timing_logs = [line for line in log_text.split("\n") if "completed in" in line]
⋮----
async def test_timing_middleware_custom_logger(self, timing_server)
⋮----
"""Test timing middleware with custom logger configuration."""
⋮----
# Create a custom logger that writes to a string buffer
log_buffer = io.StringIO()
handler = logging.StreamHandler(log_buffer)
custom_logger = logging.getLogger("custom_timing")
⋮----
# Use custom logger and log level
⋮----
# Check that our custom logger was used
log_output = log_buffer.getvalue()

================
File: tests/server/openapi/test_openapi_path_parameters.py
================
@pytest.fixture
def array_path_spec()
⋮----
"""Load a minimal OpenAPI spec with an array path parameter."""
⋮----
@pytest.fixture
def mock_client()
⋮----
"""Create a mock httpx.AsyncClient."""
client = AsyncMock(spec=httpx.AsyncClient)
# Set up a mock response
mock_response = MagicMock()
⋮----
async def test_fastmcp_from_openapi(array_path_spec, mock_client)
⋮----
"""Test creating FastMCP from OpenAPI spec with array path parameter."""
# Create FastMCP from the spec
mcp = FastMCP.from_openapi(array_path_spec, client=mock_client)
# Verify the tool was created using the MCP protocol method
tools_result = await mcp.get_tools()
tool_names = [tool.name for tool in tools_result.values()]
⋮----
async def test_array_path_parameter_handling(mock_client)
⋮----
"""Test how array path parameters are handled."""
# Create a simple route with array path parameter
route = HTTPRoute(
# Create the tool
tool = OpenAPITool(
# Test with a single value
⋮----
# Check that the path parameter is formatted correctly
# This is where the bug is: it should be '/select/monday' not '/select/[\'monday\']'
⋮----
url="/select/monday",  # This is the expected format
⋮----
# Test with multiple values
⋮----
# It should be '/select/monday,tuesday' not '/select/[\'monday\', \'tuesday\']'
⋮----
url="/select/monday,tuesday",  # This is the expected format
⋮----
async def test_integration_array_path_parameter(array_path_spec, mock_client)
⋮----
"""Integration test for array path parameters."""
⋮----
# Call the tool with a single value
⋮----
# Check the request was made correctly
⋮----
# Call the tool with multiple values
⋮----
async def test_complex_nested_array_path_parameter(mock_client)
⋮----
"""Test handling of complex nested array path parameters."""
# Create a route with a path parameter that contains nested objects in an array
⋮----
# Test with a more complex path parameter
# This would typically be serialized as JSON or a more complex format
# But for path parameters with style=simple, it should be comma-separated
complex_filters = [
# Execute the request with complex filters
⋮----
# The complex object should be properly serialized in the URL
# For path parameters, this would typically need a custom serialization strategy
# but our implementation should handle it safely
call_args = mock_client.request.call_args
# Verify the request was made
⋮----
# Get the called URL and verify it contains the serialized path parameter
called_url = call_args[1].get("url")
# Check that the path parameter is handled (we don't expect perfect serialization,
# but it should not cause errors and should maintain the array structure)
⋮----
# Check that it didn't just convert the objects to string representations
# that include the Python object syntax
⋮----
async def test_array_query_param_with_fastapi()
⋮----
"""Test array query parameters using FastAPI and FastMCP.from_fastapi integration."""
# Create a FastAPI app with a route that has an array query parameter
app = FastAPI()
⋮----
):  # Using explode=True to get days=monday&days=tuesday format
⋮----
# Create a FastMCP server from the FastAPI app
mcp = FastMCP.from_fastapi(
# Test with the client
⋮----
# Get the actual tool name first
tools = await client.list_tools()
tool_names = [tool.name for tool in tools]
⋮----
tool_name = tool_names[0]
# Single day
result = await client.call_tool(tool_name, {"days": ["monday"]})
⋮----
# Multiple days
result = await client.call_tool(tool_name, {"days": ["monday", "tuesday"]})
⋮----
async def test_array_query_parameter_format(mock_client)
⋮----
"""Test that array query parameters are formatted as comma-separated values when explode=False."""
# Create a route with array query parameter
⋮----
location="query",  # This is a query parameter
⋮----
"explode": False,  # Set explode=False to test comma-separated formatting
⋮----
# Check that the query parameter is formatted correctly
⋮----
params={"days": "monday"},  # Should be formatted as a string, not a list
⋮----
# It should be 'days=monday,tuesday' not 'days=["monday","tuesday"]'
⋮----
params={"days": "monday,tuesday"},  # Should be comma-separated
⋮----
async def test_array_query_parameter_exploded_format(mock_client)
⋮----
"""Test that array query parameters are formatted as separate parameters when explode=True."""
# Create a route with array query parameter with explode=True (default)
⋮----
"explode": True,  # Set explode=True for separate parameter serialization
⋮----
params={"days": ["monday"]},  # Should be passed as a list for explode=True
⋮----
# It should be passed as an array, which httpx will serialize as days=monday&days=tuesday
⋮----
params={"days": ["monday", "tuesday"]},  # Should be passed as a list
⋮----
def test_parameter_location_enum_handling()
⋮----
"""Test that ParameterLocation enum values are handled correctly (issue #950)."""
⋮----
# Create a mock ParameterLocation enum like the one from openapi_pydantic
class MockParameterLocation(Enum)
⋮----
PATH = "path"
QUERY = "query"
HEADER = "header"
COOKIE = "cookie"
# Test the enum handling logic directly (reproduces the fix in openapi.py)
test_cases = [
⋮----
("path", "path"),  # Also test that strings work
⋮----
# This is the enum handling logic from the fix
param_in_str = param_in.value if isinstance(param_in, Enum) else param_in

================
File: tests/server/openapi/test_openapi.py
================
class User(BaseModel)
⋮----
id: int
name: str
active: bool
class UserCreate(BaseModel)
⋮----
@pytest.fixture
def users_db() -> dict[int, User]
# route maps for GET requests
# use these to create components of all types instead of just tools
GET_ROUTE_MAPS = [
⋮----
# GET requests with path parameters go to ResourceTemplate
⋮----
# GET requests without path parameters go to Resource
⋮----
@pytest.fixture
def fastapi_app(users_db: dict[int, User]) -> FastAPI
⋮----
app = FastAPI(title="FastAPI App")
⋮----
@app.get("/users", tags=["users", "list"])
    async def get_users() -> list[User]
⋮----
"""Get all users."""
⋮----
"""Search users with optional filters."""
results = list(users_db.values())
⋮----
results = [u for u in results if name.lower() in u.name.lower()]
⋮----
results = [u for u in results if u.active == active]
⋮----
results = [u for u in results if u.id >= min_id]
⋮----
@app.get("/users/{user_id}", tags=["users", "detail"])
    async def get_user(user_id: int) -> User | None
⋮----
"""Get a user by ID."""
⋮----
@app.get("/users/{user_id}/{is_active}", tags=["users", "detail"])
    async def get_user_active_state(user_id: int, is_active: bool) -> User | None
⋮----
"""Get a user by ID and filter by active state."""
user = users_db.get(user_id)
⋮----
@app.post("/users", tags=["users", "create"])
    async def create_user(user: UserCreate) -> User
⋮----
"""Create a new user."""
user_id = max(users_db.keys()) + 1
new_user = User(id=user_id, **user.model_dump())
⋮----
@app.patch("/users/{user_id}/name", tags=["users", "update"])
    async def update_user_name(user_id: int, name: str) -> User
⋮----
"""Update a user's name."""
⋮----
@app.get("/ping", response_class=PlainTextResponse)
    async def ping() -> str
⋮----
"""Ping the server."""
⋮----
@app.get("/ping-bytes")
    async def ping_bytes() -> Response
⋮----
"""Ping the server and get a bytes response."""
⋮----
@pytest.fixture
def api_client(fastapi_app: FastAPI) -> AsyncClient
⋮----
"""Create a pre-configured httpx client for testing."""
⋮----
openapi_spec = fastapi_app.openapi()
⋮----
server = FastMCPOpenAPI(
⋮----
server = FastMCP.from_openapi(openapi_spec=fastapi_app.openapi(), client=api_client)
⋮----
async def test_create_fastapi_server_classmethod(fastapi_app: FastAPI)
⋮----
server = FastMCP.from_fastapi(fastapi_app)
⋮----
class TestTools
⋮----
"""
        By default, tools exclude GET methods
        """
server = FastMCPOpenAPI.from_fastapi(fastapi_app)
⋮----
async def test_list_tools(self, fastmcp_openapi_server: FastMCPOpenAPI)
⋮----
tools = await client.list_tools()
⋮----
"""
        The tool created by the OpenAPI server should be the same as the original
        """
⋮----
tool_response = await client.call_tool(
expected_user = User(id=4, name="David", active=False).model_dump()
⋮----
# Check that the user was created via API
response = await api_client.get("/users")
⋮----
# Check that the user was created via MCP
⋮----
user_response = await client.read_resource("resource://get_user_users/4")
response_text = user_response[0].text  # type: ignore[attr-defined]
user = json.loads(response_text)
⋮----
expected_data = dict(id=1, name="XYZ", active=True)
⋮----
# Check that the user was updated via API
⋮----
# Check that the user was updated via MCP
⋮----
user_response = await client.read_resource("resource://get_user_users/1")
⋮----
"""
        The tool created by the OpenAPI server should return a list of content.
        """
⋮----
mcp_server = FastMCPOpenAPI(
⋮----
tool_response = await client.call_tool("get_users_users_get", {})
⋮----
class TestResources
⋮----
async def test_list_resources(self, fastmcp_openapi_server: FastMCPOpenAPI)
⋮----
"""
        By default, resources exclude GET methods without parameters
        """
⋮----
resources = await client.list_resources()
⋮----
"""
        The resource created by the OpenAPI server should be the same as the original
        """
json_users = TypeAdapter(list[User]).dump_python(
⋮----
resource_response = await client.read_resource(
response_text = resource_response[0].text  # type: ignore[attr-defined]
resource = json.loads(response_text)
⋮----
"""Test reading a resource that returns bytes."""
⋮----
"""Test reading a resource that returns a string."""
⋮----
resource_response = await client.read_resource("resource://ping_ping_get")
assert resource_response[0].text == "pong"  # type: ignore[attr-defined]
class TestResourceTemplates
⋮----
"""
        By default, resource templates exclude GET methods without parameters
        """
⋮----
resource_templates = await client.list_resource_templates()
⋮----
"""
        The resource template created by the OpenAPI server should be the same as the original
        """
user_id = 2
⋮----
response = await api_client.get(f"/users/{user_id}")
⋮----
is_active = True
⋮----
response = await api_client.get(f"/users/{user_id}/{is_active}")
⋮----
class TestPrompts
⋮----
async def test_list_prompts(self, fastmcp_openapi_server: FastMCPOpenAPI)
⋮----
"""
        By default, there are no prompts.
        """
⋮----
prompts = await client.list_prompts()
⋮----
class TestTagTransfer
⋮----
"""Tests for transferring tags from OpenAPI routes to MCP objects."""
⋮----
"""Test that tags from OpenAPI routes are correctly transferred to Tools."""
# Get internal tools directly (not the public API which returns MCP.Content)
tools = await fastmcp_openapi_server._tool_manager.list_tools()
# Find the create_user and update_user_name tools
create_user_tool = next(
update_user_tool = next(
⋮----
# Check that tags from OpenAPI routes were transferred to the Tool objects
⋮----
"""Test that tags from OpenAPI routes are correctly transferred to Resources."""
# Get internal resources directly
resources_dict = await fastmcp_openapi_server._resource_manager.get_resources()
resources = list(resources_dict.values())
# Find the get_users resource
get_users_resource = next(
⋮----
# Check that tags from OpenAPI routes were transferred to the Resource object
⋮----
"""Test that tags from OpenAPI routes are correctly transferred to ResourceTemplates."""
# Get internal resource templates directly
templates_dict = (
templates = list(templates_dict.values())
# Find the get_user template
get_user_template = next(
⋮----
# Check that tags from OpenAPI routes were transferred to the ResourceTemplate object
⋮----
"""Test that tags are preserved when creating resources from templates."""
⋮----
# Manually create a resource from template
params = {"user_id": 1}
resource = await get_user_template.create_resource(
# Verify tags are preserved from template to resource
⋮----
class TestOpenAPI30Compatibility
⋮----
"""Tests for compatibility with OpenAPI 3.0 specifications."""
⋮----
@pytest.fixture
    def openapi_30_spec(self) -> dict
⋮----
"""Fixture that returns a simple OpenAPI 3.0 specification."""
⋮----
@pytest.fixture
    async def mock_30_client(self) -> httpx.AsyncClient
⋮----
"""Mock client that returns predefined responses for the 3.0 API."""
async def _responder(request)
⋮----
data = json.loads(request.content)
⋮----
product_id = request.url.path.split("/")[-1]
products = {
⋮----
transport = httpx.MockTransport(_responder)
⋮----
"""Create a FastMCPOpenAPI server from the OpenAPI 3.0 spec."""
⋮----
async def test_server_creation(self, openapi_30_server_with_all_types)
⋮----
"""Test that a server can be created from an OpenAPI 3.0 spec."""
⋮----
async def test_resource_discovery(self, openapi_30_server_with_all_types)
⋮----
"""Test that resources are correctly discovered from an OpenAPI 3.0 spec."""
⋮----
async def test_resource_template_discovery(self, openapi_30_server_with_all_types)
⋮----
"""Test that resource templates are correctly discovered from an OpenAPI 3.0 spec."""
⋮----
templates = await client.list_resource_templates()
⋮----
async def test_tool_discovery(self, openapi_30_server_with_all_types)
⋮----
"""Test that tools are correctly discovered from an OpenAPI 3.0 spec."""
⋮----
async def test_resource_access(self, openapi_30_server_with_all_types)
⋮----
"""Test reading a resource from an OpenAPI 3.0 server."""
⋮----
resource_response = await client.read_resource("resource://listProducts")
⋮----
content = json.loads(response_text)
⋮----
async def test_resource_template_access(self, openapi_30_server_with_all_types)
⋮----
"""Test reading a resource from template from an OpenAPI 3.0 server."""
⋮----
resource_response = await client.read_resource("resource://getProduct/p1")
⋮----
async def test_tool_execution(self, openapi_30_server_with_all_types)
⋮----
"""Test executing a tool from an OpenAPI 3.0 server."""
⋮----
result = await client.call_tool(
# Result should be a text content
⋮----
product = json.loads(result.content[0].text)  # type: ignore[attr-defined]
⋮----
class TestOpenAPI31Compatibility
⋮----
"""Tests for compatibility with OpenAPI 3.1 specifications."""
⋮----
@pytest.fixture
    def openapi_31_spec(self) -> dict
⋮----
"""Fixture that returns a simple OpenAPI 3.1 specification."""
⋮----
@pytest.fixture
    async def mock_31_client(self) -> httpx.AsyncClient
⋮----
"""Mock client that returns predefined responses for the 3.1 API."""
⋮----
order_id = request.url.path.split("/")[-1]
orders = {
⋮----
"""Create a FastMCPOpenAPI server from the OpenAPI 3.1 spec."""
⋮----
async def test_server_creation(self, openapi_31_server_with_all_types)
⋮----
"""Test that a server can be created from an OpenAPI 3.1 spec."""
⋮----
async def test_resource_discovery(self, openapi_31_server_with_all_types)
⋮----
"""Test that resources are correctly discovered from an OpenAPI 3.1 spec."""
⋮----
async def test_resource_template_discovery(self, openapi_31_server_with_all_types)
⋮----
"""Test that resource templates are correctly discovered from an OpenAPI 3.1 spec."""
⋮----
async def test_tool_discovery(self, openapi_31_server_with_all_types)
⋮----
"""Test that tools are correctly discovered from an OpenAPI 3.1 spec."""
⋮----
async def test_resource_access(self, openapi_31_server_with_all_types)
⋮----
"""Test reading a resource from an OpenAPI 3.1 server."""
⋮----
resource_response = await client.read_resource("resource://listOrders")
⋮----
async def test_resource_template_access(self, openapi_31_server_with_all_types)
⋮----
"""Test reading a resource from template from an OpenAPI 3.1 server."""
⋮----
resource_response = await client.read_resource("resource://getOrder/o1")
⋮----
async def test_tool_execution(self, openapi_31_server_with_all_types)
⋮----
"""Test executing a tool from an OpenAPI 3.1 server."""
⋮----
order = json.loads(result.content[0].text)  # type: ignore[attr-dict]
⋮----
"""Test that empty and None query parameters are not sent in the request."""
# Create a TransportAdapter to track requests
class RequestCapture(httpx.AsyncBaseTransport)
⋮----
def __init__(self, wrapped)
async def handle_async_request(self, request)
# Use our transport adapter to wrap the original one
capture = RequestCapture(api_client._transport)
⋮----
# Create the OpenAPI server with new route map to make search endpoint a tool
⋮----
# Call the search tool with mixed parameter values
⋮----
"name": "",  # Empty string should be excluded
"active": None,  # None should be excluded
"min_id": 2,  # Has value, should be included
⋮----
# Verify that the request URL only has min_id parameter
⋮----
request = capture.requests[-1]  # Get the last request
# URL should only contain min_id=2, not name= or active=
url = str(request.url)
⋮----
# More direct check - parse the URL to examine query params
⋮----
parsed_url = urlparse(url)
query_params = parse_qs(parsed_url.query)
⋮----
"""Test that None values for path parameters are properly rejected."""
# Create the OpenAPI server
⋮----
# Create a client and try to call a tool with a None path parameter
⋮----
# get_user has a required path parameter user_id
⋮----
"user_id": None,  # This should cause an error
⋮----
class TestDescriptionPropagation
⋮----
"""Tests for OpenAPI description propagation to FastMCP components.
    Each test focuses on a single, specific behavior to make it immediately clear
    what's broken when a test fails.
    """
⋮----
@pytest.fixture
    def simple_openapi_spec(self) -> dict
⋮----
"""Create a minimal OpenAPI spec with obvious test descriptions."""
⋮----
@pytest.fixture
    async def mock_client(self) -> httpx.AsyncClient
⋮----
"""Create a mock client that returns simple responses."""
⋮----
item_id = request.url.path.split("/")[-1]
⋮----
@pytest.fixture
    async def simple_mcp_server(self, simple_openapi_spec, mock_client)
⋮----
"""Create a FastMCPOpenAPI server with the simple test spec."""
⋮----
# --- RESOURCE TESTS ---
⋮----
"""Test that a Resource includes the route description."""
resources = list(
list_resource = next((r for r in resources if r.name == "listItems"), None)
⋮----
"""Test that a Resource includes the response description."""
⋮----
"""Test that a Resource description includes response model field descriptions."""
⋮----
description = list_resource.description or ""
⋮----
# --- RESOURCE TEMPLATE TESTS ---
⋮----
"""Test that a ResourceTemplate includes the route description."""
⋮----
get_template = next((t for t in templates if t.name == "getItem"), None)
⋮----
"""Test that a ResourceTemplate includes the function docstring."""
⋮----
"""Test that a ResourceTemplate includes path parameter descriptions."""
⋮----
"""Test that a ResourceTemplate includes query parameter descriptions."""
⋮----
"""Test that a ResourceTemplate's parameter schema includes parameter descriptions."""
⋮----
# --- TOOL TESTS ---
async def test_tool_includes_route_description(self, simple_mcp_server: FastMCP)
⋮----
"""Test that a Tool includes the route description."""
tools_dict = await simple_mcp_server._tool_manager.get_tools()
tools = list(tools_dict.values())
create_tool = next((t for t in tools if t.name == "createItem"), None)
⋮----
async def test_tool_includes_function_docstring(self, simple_mcp_server: FastMCP)
⋮----
"""Test that a Tool includes the function docstring."""
⋮----
description = create_tool.description or ""
⋮----
"""Test that a Tool's parameter schema includes property descriptions from request model."""
⋮----
# --- CLIENT API TESTS ---
async def test_client_api_resource_description(self, simple_mcp_server: FastMCP)
⋮----
"""Test that Resource descriptions are accessible via the client API."""
⋮----
resource_description = list_resource.description or ""
⋮----
async def test_client_api_template_description(self, simple_mcp_server: FastMCP)
⋮----
"""Test that ResourceTemplate descriptions are accessible via the client API."""
⋮----
template_description = get_template.description or ""
⋮----
async def test_client_api_tool_description(self, simple_mcp_server: FastMCP)
⋮----
"""Test that Tool descriptions are accessible via the client API."""
⋮----
tool_description = create_tool.description or ""
⋮----
async def test_client_api_tool_parameter_schema(self, simple_mcp_server: FastMCP)
⋮----
"""Test that Tool parameter schemas are accessible via the client API."""
⋮----
class TestFastAPIDescriptionPropagation
⋮----
"""Tests for FastAPI docstring and annotation propagation to FastMCP components.
    Each test focuses on a single, specific behavior to make it immediately clear
    what's broken when a test fails.
    """
⋮----
@pytest.fixture
    def fastapi_app_with_descriptions(self) -> FastAPI
⋮----
"""Create a simple FastAPI app with docstrings and annotations."""
⋮----
app = FastAPI(title="Test FastAPI App")
class Item(BaseModel)
⋮----
name: str = Field(..., description="ITEM_NAME_DESCRIPTION")
price: float = Field(..., description="ITEM_PRICE_DESCRIPTION")
class ItemResponse(BaseModel)
⋮----
id: str = Field(..., description="ITEM_RESPONSE_ID_DESCRIPTION")
name: str = Field(..., description="ITEM_RESPONSE_NAME_DESCRIPTION")
price: float = Field(..., description="ITEM_RESPONSE_PRICE_DESCRIPTION")
⋮----
@app.get("/items", tags=["items"])
        async def list_items() -> list[ItemResponse]
⋮----
"""FUNCTION_LIST_DESCRIPTION
            Returns a list of items.
            """
⋮----
"""FUNCTION_GET_DESCRIPTION
            Gets a specific item by ID.
            Args:
                item_id: The ID of the item to retrieve
                fields: Optional fields to include
            """
⋮----
@app.post("/items", tags=["items", "create"])
        async def create_item(item: Item) -> ItemResponse
⋮----
"""FUNCTION_CREATE_DESCRIPTION
            Creates a new item.
            Body:
                Item object with name and price
            """
⋮----
@pytest.fixture
    async def fastapi_server(self, fastapi_app_with_descriptions)
⋮----
"""Create a FastMCP server from the FastAPI app with custom route mappings."""
# First create from FastAPI app to get the OpenAPI spec
openapi_spec = fastapi_app_with_descriptions.openapi()
# Debug: check the operationIds in the OpenAPI spec
⋮----
if method != "parameters":  # Skip non-HTTP method keys
operation_id = details.get("operationId", "no_operation_id")
⋮----
# Create custom route mappings
route_maps = [
⋮----
# Map GET /items to Resource
⋮----
# Map GET /items/{item_id} to ResourceTemplate
⋮----
# Map POST /items to Tool
⋮----
# Create FastMCP server with the OpenAPI spec and custom route mappings
⋮----
# Debug: print all components created
⋮----
resources_dict = await server._resource_manager.get_resources()
⋮----
templates_dict = await server._resource_manager.get_resource_templates()
⋮----
tools = await server._tool_manager.list_tools()
⋮----
async def test_resource_includes_function_docstring(self, fastapi_server: FastMCP)
⋮----
"""Test that a Resource includes the function docstring."""
resources_dict = await fastapi_server._resource_manager.get_resources()
⋮----
# Now checking for the get_items operation ID rather than list_items
list_resource = next((r for r in resources if "items_get" in r.name), None)
⋮----
"""Test that a Resource description includes basic response information.
        Note: FastAPI doesn't reliably include Pydantic field descriptions in the OpenAPI schema,
        so we can only check for basic response information being present.
        """
⋮----
# Check that at least the response information is included
⋮----
# We've already verified in TestDescriptionPropagation that when descriptions
# are present in the OpenAPI schema, they are properly included in the component description
async def test_template_includes_function_docstring(self, fastapi_server: FastMCP)
⋮----
templates_dict = await fastapi_server._resource_manager.get_resource_templates()
⋮----
get_template = next((t for t in templates if "get_item_items" in t.name), None)
⋮----
description = get_template.description or ""
⋮----
"""Test that a ResourceTemplate includes path parameter descriptions.
        Note: Currently, FastAPI parameter descriptions using Annotated[type, Field(description=...)]
        are not properly propagated to the OpenAPI schema. The parameters appear but without the description.
        """
⋮----
# Just test that parameters are included at all
⋮----
"""Test that a ResourceTemplate includes query parameter descriptions.
        Note: Currently, FastAPI parameter descriptions using Annotated[type, Field(description=...)]
        are not properly propagated to the OpenAPI schema. The parameters appear but without the description.
        """
⋮----
async def test_tool_includes_function_docstring(self, fastapi_server: FastMCP)
⋮----
tools_dict = await fastapi_server._tool_manager.get_tools()
⋮----
create_tool = next(
⋮----
"""Test that a Tool's parameter schema includes property descriptions from request model.
        Note: Currently, model field descriptions defined in Pydantic models using Field(description=...)
        may not be consistently propagated into the FastAPI OpenAPI schema and thus not into the tool's
        parameter schema.
        """
⋮----
# We don't test for the description field content as it may not be consistently propagated
async def test_client_api_resource_description(self, fastapi_server: FastMCP)
async def test_client_api_template_description(self, fastapi_server: FastMCP)
⋮----
get_template = next(
⋮----
async def test_client_api_tool_description(self, fastapi_server: FastMCP)
async def test_client_api_tool_parameter_schema(self, fastapi_server: FastMCP)
⋮----
# We don't test for the description field content as it may not be consistently propagated
class TestReprMethods
⋮----
"""Tests for the custom __repr__ methods of OpenAPI objects."""
async def test_openapi_tool_repr(self, fastmcp_openapi_server: FastMCPOpenAPI)
⋮----
"""Test that OpenAPITool's __repr__ method works without recursion errors."""
⋮----
tool = next(iter(tools))
# Verify repr doesn't cause recursion and contains expected elements
tool_repr = repr(tool)
⋮----
async def test_openapi_resource_repr(self, fastmcp_openapi_server: FastMCPOpenAPI)
⋮----
"""Test that OpenAPIResource's __repr__ method works without recursion errors."""
⋮----
resource = next(iter(resources))
⋮----
resource_repr = repr(resource)
⋮----
"""Test that OpenAPIResourceTemplate's __repr__ method works without recursion errors."""
⋮----
template = next(iter(templates))
⋮----
template_repr = repr(template)
⋮----
class TestEnumHandling
⋮----
"""Tests for handling enum parameters in OpenAPI schemas."""
async def test_enum_parameter_schema(self)
⋮----
"""Test that enum parameters are properly handled in tool parameter schemas."""
# Define an enum just like in example.py
class QueryEnum(str, Enum)
⋮----
foo = "foo"
bar = "bar"
baz = "baz"
# Create a minimal FastAPI app with an endpoint using the enum
app = FastAPI()
⋮----
# Create a client for the app
client = AsyncClient(transport=ASGITransport(app=app), base_url="http://test")
# Create the FastMCPOpenAPI server from the app
openapi_spec = app.openapi()
⋮----
# Get the tools from the server
⋮----
# Find the read_item tool
read_item_tool = next((t for t in tools if t.name == "read_item_items"), None)
# Verify the tool exists
⋮----
# Check that the parameters include the enum reference
⋮----
# Check for the anyOf with $ref to the enum definition
query_param = read_item_tool.parameters["properties"]["query"]
⋮----
# Find the ref in the anyOf list
ref_found = False
⋮----
ref_found = True
⋮----
# Check that the $defs section exists and contains the enum definition
⋮----
# Verify the enum definition
enum_def = read_item_tool.parameters["$defs"]["QueryEnum"]
⋮----
class TestRouteMapWildcard
⋮----
"""Tests for wildcard RouteMap methods functionality."""
⋮----
@pytest.fixture
    def basic_openapi_spec(self) -> dict
⋮----
"""Create a minimal OpenAPI spec with different HTTP methods."""
⋮----
@pytest.fixture
    async def mock_basic_client(self) -> httpx.AsyncClient
⋮----
"""Create a simple mock client."""
⋮----
"""Test that a RouteMap with methods='*' matches all HTTP methods."""
# Create a single route map with wildcard method
route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
mcp = FastMCPOpenAPI(
# All operations should be mapped to tools
tools = await mcp._tool_manager.list_tools()
tool_names = {tool.name for tool in tools}
# Check that all 4 operations became tools
expected_tools = {"getUsers", "createUser", "getPosts", "createPost"}
⋮----
class TestRouteMapTags
⋮----
"""Tests for RouteMap tags functionality."""
⋮----
@pytest.fixture
    def tagged_openapi_spec(self) -> dict
⋮----
"""Create an OpenAPI spec with various tags for testing."""
⋮----
async def test_tags_as_tools(self, tagged_openapi_spec, mock_client)
⋮----
"""Test that routes with specific tags are converted to tools."""
# Convert routes with "admin" tag to tools
⋮----
# Check that admin-tagged routes are tools
tools_dict = await server._tool_manager.get_tools()
tool_names = {t.name for t in tools_dict.values()}
⋮----
resource_names = {r.name for r in resources_dict.values()}
# Routes with "admin" tag should be tools
⋮----
# Routes without "admin" tag should be resources
⋮----
async def test_exclude_tags(self, tagged_openapi_spec, mock_client)
⋮----
"""Test that routes with specific tags are excluded."""
# Exclude routes with "internal" tag
⋮----
# Check that internal-tagged routes are excluded
⋮----
# Internal-tagged route should be excluded
⋮----
# Other routes should still be present
⋮----
async def test_multiple_tags_and_condition(self, tagged_openapi_spec, mock_client)
⋮----
"""Test that routes must have ALL specified tags (AND condition)."""
# Routes must have BOTH "users" AND "admin" tags
⋮----
# Only createUser has both "users" AND "admin" tags
⋮----
# Other routes should be resources
assert "getUsers" in resource_names  # has "users" but not "admin"
assert "getAdminStats" in resource_names  # has "admin" but not "users"
⋮----
async def test_pattern_and_tags_combination(self, tagged_openapi_spec, mock_client)
⋮----
"""Test that both pattern and tags must be satisfied."""
# Routes matching pattern AND having specific tags
⋮----
# Only getAdminStats matches both /admin/ pattern AND "admin" tag
⋮----
# createUser has "admin" tag but doesn't match pattern, so it becomes a tool via POST rule
⋮----
# Other routes should be resources (GET)
⋮----
async def test_empty_tags_ignored(self, tagged_openapi_spec, mock_client)
⋮----
"""Test that empty tags set is ignored (matches all routes)."""
# Empty tags should match all routes
⋮----
# All routes should be tools since empty tags matches everything
expected_tools = {
⋮----
class TestMCPNames
⋮----
"""Tests for the mcp_names dictionary functionality."""
⋮----
@pytest.fixture
    def mcp_names_openapi_spec(self) -> dict
⋮----
"""OpenAPI spec with various operationIds for testing naming strategies."""
⋮----
"""Mock client for testing."""
⋮----
async def test_mcp_names_custom_mapping(self, mcp_names_openapi_spec, mock_client)
⋮----
"""Test that mcp_names dictionary provides custom names for components."""
mcp_names = {
⋮----
# Check tools use custom names
⋮----
# Check resource templates use custom names
⋮----
template_names = {template.name for template in templates_dict.values()}
⋮----
# Check resources use custom names
⋮----
resource_names = {resource.name for resource in resources_dict.values()}
⋮----
"""Test fallback to operationId up to double underscore when not in mcp_names."""
# Only provide mapping for one operationId
⋮----
# Custom mapped name should be used
⋮----
# Unmapped operationIds should use short version (up to __)
⋮----
async def test_names_are_slugified(self, mcp_names_openapi_spec, mock_client)
⋮----
"""Test that names are properly slugified (spaces, special chars removed)."""
⋮----
resource_names = {
# Special chars and spaces should be slugified
slugified_name = next(
⋮----
# Should not contain special characters or spaces
⋮----
"""Test that names are truncated to 56 characters maximum."""
⋮----
# Check all component types
all_names = []
⋮----
# All names should be 56 characters or less
⋮----
# Verify that the long operationId was actually truncated
long_name = next((name for name in all_names if len(name) > 50), None)
⋮----
"""Test mcp_names works with FastMCP.from_openapi() classmethod."""
⋮----
server = FastMCP.from_openapi(
⋮----
async def test_mcp_names_with_from_fastapi_classmethod(self)
⋮----
"""Test mcp_names works with FastMCP.from_fastapi() classmethod."""
⋮----
app = FastAPI(title="FastAPI MCP Names Test")
class User(BaseModel)
⋮----
@app.get("/users", operation_id="list_users__with_filters")
        async def get_users() -> list[User]
⋮----
@app.post("/users", operation_id="create_user__admin_required")
        async def create_user(user: User) -> User
⋮----
server = FastMCP.from_fastapi(
⋮----
"""Test that custom names in mcp_names are also truncated to 56 characters."""
# Provide a custom name that's longer than 56 characters
very_long_custom_name = "this_is_a_very_long_custom_name_that_exceeds_fifty_six_characters_and_should_be_truncated"
⋮----
# Find the resource that should have the custom name
truncated_name = next(
⋮----
)  # Should be exactly 56 since original was longer
class TestRouteMapMCPTags
⋮----
"""Tests for RouteMap mcp_tags functionality."""
⋮----
@pytest.fixture
    def simple_fastapi_app(self) -> FastAPI
⋮----
"""Create a simple FastAPI app for testing mcp_tags."""
app = FastAPI(title="MCP Tags Test API")
⋮----
@app.get("/users", tags=["users"])
        async def get_users()
⋮----
"""Get all users."""
⋮----
@app.get("/users/{user_id}", tags=["users"])
        async def get_user(user_id: int)
⋮----
"""Get user by ID."""
⋮----
@app.post("/users", tags=["users"])
        async def create_user(name: str)
⋮----
"""Create a new user."""
⋮----
async def test_mcp_tags_added_to_tools(self, simple_fastapi_app, mock_client)
⋮----
"""Test that mcp_tags are added to Tools created from routes."""
# Create route map that adds custom tags to POST endpoints
⋮----
# Default mapping for other routes
⋮----
# Get the POST tool
⋮----
create_user_tool = next((t for t in tools if "create_user" in t.name), None)
⋮----
# Check that both original tags and mcp_tags are present
assert "users" in create_user_tool.tags  # Original OpenAPI tag
assert "custom" in create_user_tool.tags  # Added via mcp_tags
assert "api-write" in create_user_tool.tags  # Added via mcp_tags
async def test_mcp_tags_added_to_resources(self, simple_fastapi_app, mock_client)
⋮----
"""Test that mcp_tags are added to Resources created from routes."""
# Create route map that adds custom tags to GET endpoints without path params
⋮----
pattern=r"^/users$",  # Only match /users, not /users/{id}
⋮----
# Get the resource
⋮----
get_users_resource = next((r for r in resources if "get_users" in r.name), None)
⋮----
assert "users" in get_users_resource.tags  # Original OpenAPI tag
assert "list-data" in get_users_resource.tags  # Added via mcp_tags
assert "public-api" in get_users_resource.tags  # Added via mcp_tags
⋮----
"""Test that mcp_tags are added to ResourceTemplates created from routes."""
# Create route map that adds custom tags to GET endpoints with path params
⋮----
pattern=r".*\{.*\}.*",  # Match routes with path parameters
⋮----
# Get the resource template
⋮----
get_user_template = next((t for t in templates if "get_user" in t.name), None)
⋮----
assert "users" in get_user_template.tags  # Original OpenAPI tag
assert "detail-view" in get_user_template.tags  # Added via mcp_tags
assert "parameterized" in get_user_template.tags  # Added via mcp_tags
⋮----
"""Test that different route maps can add different mcp_tags."""
# Multiple route maps with different mcp_tags
⋮----
# First priority: POST requests get write-related tags
⋮----
# Second priority: GET with path params get detail tags
⋮----
# Third priority: Other GET requests get list tags
⋮----
# Check tool tags
⋮----
create_tool = next((t for t in tools if "create_user" in t.name), None)
⋮----
# Check resource template tags
⋮----
detail_template = next((t for t in templates if "get_user" in t.name), None)
⋮----
# Check resource tags
⋮----
list_resource = next((r for r in resources if "get_users" in r.name), None)
⋮----
class TestGlobalTagsParameter
⋮----
"""Tests for the global tags parameter on from_openapi and from_fastapi class methods."""
⋮----
"""Create a simple FastAPI app for testing global tags."""
app = FastAPI(title="Global Tags Test API")
⋮----
@app.get("/items", tags=["items"])
        async def get_items()
⋮----
"""Get all items."""
⋮----
@app.get("/items/{item_id}", tags=["items"])
        async def get_item(item_id: int)
⋮----
"""Get item by ID."""
⋮----
@app.post("/items", tags=["items"])
        async def create_item(name: str)
⋮----
"""Create a new item."""
⋮----
async def test_from_fastapi_adds_global_tags(self, simple_fastapi_app)
⋮----
"""Test that from_fastapi adds global tags to all components."""
global_tags = {"global", "api-v1"}
⋮----
# Check tool has both original and global tags
tools = await server.get_tools()
create_item_tool = tools["create_item_items_post"]
assert "items" in create_item_tool.tags  # Original OpenAPI tag
assert "global" in create_item_tool.tags  # Global tag
assert "api-v1" in create_item_tool.tags  # Global tag
# Check resource has both original and global tags
resources = await server.get_resources()
get_items_resource = resources["resource://get_items_items_get"]
assert "items" in get_items_resource.tags  # Original OpenAPI tag
assert "global" in get_items_resource.tags  # Global tag
assert "api-v1" in get_items_resource.tags  # Global tag
# Check resource template has both original and global tags
templates = await server.get_resource_templates()
get_item_template = templates["resource://get_item_items/{item_id}"]
assert "items" in get_item_template.tags  # Original OpenAPI tag
assert "global" in get_item_template.tags  # Global tag
assert "api-v1" in get_item_template.tags  # Global tag
async def test_from_openapi_adds_global_tags(self, simple_fastapi_app, mock_client)
⋮----
"""Test that from_openapi adds global tags to all components."""
global_tags = {"openapi-global", "service"}
⋮----
assert "openapi-global" in create_item_tool.tags  # Global tag
assert "service" in create_item_tool.tags  # Global tag
⋮----
assert "openapi-global" in get_items_resource.tags  # Global tag
assert "service" in get_items_resource.tags  # Global tag
⋮----
assert "openapi-global" in get_item_template.tags  # Global tag
assert "service" in get_item_template.tags  # Global tag
⋮----
"""Test that global tags combine with both OpenAPI tags and RouteMap mcp_tags."""
global_tags = {"global"}
route_map_tags = {"route-specific"}
⋮----
# Check that all three types of tags are present on the tool
⋮----
assert "route-specific" in create_item_tool.tags  # RouteMap mcp_tag
# Check that resource only has OpenAPI and global tags (no route-specific since different RouteMap)
⋮----
assert "route-specific" not in get_items_resource.tags  # Not from this RouteMap

================
File: tests/server/openapi/test_route_map_fn.py
================
"""Tests for the route_map_fn and component_fn functionality in FastMCPOpenAPI."""
⋮----
@pytest.fixture
def sample_openapi_spec()
⋮----
"""Sample OpenAPI spec for testing."""
⋮----
@pytest.fixture
def http_client()
⋮----
"""HTTP client for testing."""
⋮----
def test_route_map_fn_none(sample_openapi_spec, http_client)
⋮----
"""Test that server works correctly when route_map_fn is None."""
server = FastMCPOpenAPI(
⋮----
route_map_fn=None,  # Explicitly set to None
⋮----
def test_route_map_fn_custom_type_conversion(sample_openapi_spec, http_client)
⋮----
"""Test that route_map_fn can convert route types."""
def admin_routes_to_tools(route, mcp_type)
⋮----
"""Convert all admin routes to tools."""
⋮----
# Admin GET route should be converted to tool instead of resource
tools = server._tool_manager._tools
⋮----
# Admin POST route should still be a tool (was already)
⋮----
def test_component_fn_customization(sample_openapi_spec, http_client)
⋮----
"""Test that component_fn can customize components."""
def customize_components(route, component)
⋮----
"""Customize components based on route."""
⋮----
# Add custom tags to all components
⋮----
# Modify tool descriptions
⋮----
# Modify resource descriptions
⋮----
# Check that components were customized
⋮----
resources = server._resource_manager._resources
# Tools should have custom tags and modified descriptions
⋮----
# Resources should have custom tags and modified descriptions
⋮----
def test_route_map_fn_returns_none(sample_openapi_spec, http_client)
⋮----
"""Test that route_map_fn returning None uses defaults."""
def always_return_none(route, mcp_type)
⋮----
"""Always return None to use defaults."""
⋮----
# Should have default behavior
⋮----
# Check that components were created with default mapping
⋮----
templates = server._resource_manager._templates
# Should have tools, resources, and templates based on default mapping
⋮----
def test_route_map_fn_called_for_excluded_routes(sample_openapi_spec, http_client)
⋮----
"""Test that route_map_fn is called for excluded routes and can rescue them."""
# Exclude all admin routes
route_maps = [
called_routes = []
def track_calls_and_rescue(route, mcp_type)
⋮----
"""Track which routes the function is called for and rescue some excluded routes."""
⋮----
# Rescue the admin GET route by converting it to a tool
⋮----
return None  # Accept the assignment for other routes
⋮----
# route_map_fn should now be called for all routes, including excluded admin routes
⋮----
# The rescued admin GET route should now be a tool
⋮----
# The admin POST route should still be excluded (not rescued)
⋮----
def test_route_map_fn_error_handling(sample_openapi_spec, http_client)
⋮----
"""Test that errors in route_map_fn are handled gracefully."""
def error_function(route, mcp_type)
⋮----
"""Function that raises an error."""
⋮----
# Should not raise an error, but log a warning
⋮----
# Server should still be created successfully
⋮----
def test_component_fn_error_handling(sample_openapi_spec, http_client)
⋮----
"""Test that errors in component_fn are handled gracefully."""
def error_function(route, component)
⋮----
def test_combined_route_map_fn_and_component_fn(sample_openapi_spec, http_client)
⋮----
"""Test using both route_map_fn and component_fn together."""
def route_mapper(route, mcp_type)
⋮----
"""Convert admin routes to tools."""
⋮----
def component_customizer(route, component)
⋮----
"""Add admin tag to admin components."""
⋮----
# Check that both functions worked
⋮----
# Admin GET route should be converted to tool
⋮----
admin_tool = tools["getAdminSettings"]
⋮----
# Admin POST route should have admin tag
admin_post_tool = tools["updateAdminSettings"]
⋮----
def test_route_map_fn_signature_validation()
⋮----
"""Test that route_map_fn has the correct signature."""
⋮----
# This is more of a type checking test
⋮----
# Should be assignable to RouteMapFn type
fn: RouteMapFn = valid_route_map_fn
⋮----
def test_component_fn_signature_validation()
⋮----
"""Test that component_fn has the correct signature."""
⋮----
# Should be assignable to ComponentFn type
fn: ComponentFn = valid_component_fn
⋮----
def test_route_map_fn_can_rescue_excluded_routes(sample_openapi_spec, http_client)
⋮----
"""Test that route_map_fn can rescue routes that were excluded by RouteMap."""
# Exclude ALL routes by default
⋮----
RouteMap(mcp_type=MCPType.EXCLUDE)  # Catch-all exclusion
⋮----
def rescue_users_routes(route, mcp_type)
⋮----
"""Rescue only user-related routes."""
⋮----
# Rescue user routes as tools
⋮----
# Let everything else stay excluded
⋮----
# Only user routes should be rescued as tools
⋮----
# Should have user-related tools
⋮----
# Should have no resources or templates (everything excluded except rescued tools)
⋮----
# Admin and API routes should still be excluded

================
File: tests/server/test_app_state.py
================
def test_http_app_sets_mcp_server_state()
⋮----
server = FastMCP(name="StateTest")
app = server.http_app()
⋮----
def test_http_app_sse_sets_mcp_server_state()
⋮----
app = server.http_app(transport="sse")
⋮----
def test_create_streamable_http_app_sets_state()
⋮----
app = create_streamable_http_app(server, "/mcp/")
⋮----
def test_create_sse_app_sets_state()
⋮----
app = create_sse_app(server, message_path="/message", sse_path="/sse/")

================
File: tests/server/test_auth_integration.py
================
# Mock OAuth provider for testing
class MockOAuthProvider(OAuthAuthorizationServerProvider)
⋮----
def __init__(self)
⋮----
self.auth_codes = {}  # code -> {client_id, code_challenge, redirect_uri}
self.tokens = {}  # token -> {client_id, scopes, expires_at}
self.refresh_tokens = {}  # refresh_token -> access_token
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None
async def register_client(self, client_info: OAuthClientInformationFull)
⋮----
# toy authorize implementation which just immediately generates an authorization
# code and completes the redirect
code = AuthorizationCode(
⋮----
# Generate an access token and refresh token
access_token = f"access_{secrets.token_hex(32)}"
refresh_token = f"refresh_{secrets.token_hex(32)}"
# Store the tokens
⋮----
# Remove the used code
⋮----
old_access_token = self.refresh_tokens.get(refresh_token)
⋮----
token_info = self.tokens.get(old_access_token)
⋮----
# Create a RefreshToken object that matches what is expected in later code
refresh_obj = RefreshToken(
⋮----
# Check if refresh token exists
⋮----
old_access_token = self.refresh_tokens[refresh_token.token]
# Check if the access token exists
⋮----
# Check if the token was issued to this client
token_info = self.tokens[old_access_token]
⋮----
# Generate a new access token and refresh token
new_access_token = f"access_{secrets.token_hex(32)}"
new_refresh_token = f"refresh_{secrets.token_hex(32)}"
# Store the new tokens
⋮----
# Remove the old tokens
⋮----
async def load_access_token(self, token: str) -> AccessToken | None
⋮----
token_info = self.tokens.get(token)
# Check if token is expired
# if token_info.expires_at < int(time.time()):
#     raise InvalidTokenError("Access token has expired")
⋮----
async def revoke_token(self, token: AccessToken | RefreshToken) -> None
⋮----
# Remove the refresh token
⋮----
# Remove the access token
⋮----
# Also remove any refresh tokens that point to this access token
⋮----
@pytest.fixture
def mock_oauth_provider()
⋮----
@pytest.fixture
def auth_app(mock_oauth_provider)
⋮----
# Create auth router
auth_routes = create_auth_routes(
# Create Starlette app
app = Starlette(routes=auth_routes)
⋮----
@pytest.fixture
async def test_client(auth_app)
⋮----
@pytest.fixture
async def registered_client(test_client: httpx.AsyncClient, request)
⋮----
"""Create and register a test client.
    Parameters can be customized via indirect parameterization:
    @pytest.mark.parametrize("registered_client",
                            [{"grant_types": ["authorization_code"]}],
                            indirect=True)
    """
# Default client metadata
client_metadata = {
# Override with any parameters from the test
⋮----
response = await test_client.post("/register", json=client_metadata)
⋮----
client_info = response.json()
⋮----
@pytest.fixture
def pkce_challenge()
⋮----
"""Create a PKCE challenge with code_verifier and code_challenge."""
code_verifier = "some_random_verifier_string"
code_challenge = (
⋮----
@pytest.fixture
async def auth_code(test_client, registered_client, pkce_challenge, request)
⋮----
"""Get an authorization code.
    Parameters can be customized via indirect parameterization:
    @pytest.mark.parametrize("auth_code",
                            [{"redirect_uri": "https://client.example.com/other-callback"}],
                            indirect=True)
    """
# Default authorize params
auth_params = {
⋮----
response = await test_client.get("/authorize", params=auth_params)
⋮----
# Extract the authorization code
redirect_url = response.headers["location"]
parsed_url = urlparse(redirect_url)
query_params = parse_qs(parsed_url.query)
⋮----
auth_code = query_params["code"][0]
⋮----
@pytest.fixture
async def tokens(test_client, registered_client, auth_code, pkce_challenge, request)
⋮----
"""Exchange authorization code for tokens.
    Parameters can be customized via indirect parameterization:
    @pytest.mark.parametrize("tokens",
                            [{"code_verifier": "wrong_verifier"}],
                            indirect=True)
    """
# Default token request params
token_params = {
⋮----
response = await test_client.post("/token", data=token_params)
# Don't assert success here since some tests will intentionally cause errors
⋮----
class TestAuthEndpoints
⋮----
async def test_metadata_endpoint(self, test_client: httpx.AsyncClient)
⋮----
"""Test the OAuth 2.1 metadata endpoint."""
⋮----
response = await test_client.get("/.well-known/oauth-authorization-server")
⋮----
metadata = response.json()
⋮----
async def test_token_validation_error(self, test_client: httpx.AsyncClient)
⋮----
"""Test token endpoint error - validation error."""
# Missing required fields
response = await test_client.post(
⋮----
# Missing code, code_verifier, client_id, etc.
⋮----
error_response = response.json()
⋮----
)  # Contains validation error messages
⋮----
"""Test token endpoint error - authorization code does not exist."""
# Try to use a non-existent authorization code
⋮----
"""Test token endpoint error - authorization code has expired."""
# Get the current time for our time mocking
current_time = time.time()
# Find the auth code object
code_value = auth_code["code"]
found_code = None
⋮----
found_code = code_obj
⋮----
# Authorization codes are typically short-lived (5 minutes = 300 seconds)
# So we'll mock time to be 10 minutes (600 seconds) in the future
⋮----
# Try to use the expired authorization code
⋮----
"""Test token endpoint error - redirect URI mismatch."""
# Try to use the code with a different redirect URI
⋮----
# Different from the one used in /authorize
⋮----
"""Test token endpoint error - PKCE code verifier mismatch."""
# Try to use the code with an incorrect code verifier
⋮----
# Different from the one used to create challenge
⋮----
async def test_token_invalid_refresh_token(self, test_client, registered_client)
⋮----
"""Test token endpoint error - refresh token does not exist."""
# Try to use a non-existent refresh token
⋮----
"""Test token endpoint error - refresh token has expired."""
# Step 1: First, let's create a token and refresh token at the current time
⋮----
# Exchange authorization code for tokens normally
token_response = await test_client.post(
⋮----
tokens = token_response.json()
refresh_token = tokens["refresh_token"]
# Step 2: Time travel forward 4 hours (tokens expire in 1 hour by default)
# Mock the time.time() function to return a value 4 hours in the future
⋮----
):  # 4 hours = 14400 seconds
# Try to use the refresh token which should now be considered expired
⋮----
# In the "future", the token should be considered expired
⋮----
"""Test token endpoint error - invalid scope in refresh token request."""
# Exchange authorization code for tokens
⋮----
# Try to use refresh token with an invalid scope
⋮----
"scope": "read write invalid_scope",  # Adding an invalid scope
⋮----
"""Test client registration."""
⋮----
# Verify that the client was registered
# assert await mock_oauth_provider.clients_store.get_client(
#     client_info["client_id"]
# ) is not None
⋮----
"""Test client registration with missing required fields."""
# Missing redirect_uris which is a required field
⋮----
error_data = response.json()
⋮----
"""Test client registration with invalid URIs."""
# Invalid redirect_uri format
⋮----
"""Test client registration with empty redirect_uris array."""
⋮----
"redirect_uris": [],  # Empty array
⋮----
"""Test the authorization endpoint using POST with form-encoded data."""
# Register a client
⋮----
# Use POST with form-encoded data for authorization
⋮----
# Extract the authorization code from the redirect URL
⋮----
"""Test the full authorization flow."""
# 1. Register a client
⋮----
# 2. Request authorization using GET with query params
response = await test_client.get(
⋮----
# 3. Extract the authorization code from the redirect URL
⋮----
# 4. Exchange the authorization code for tokens
⋮----
token_response = response.json()
⋮----
# 5. Verify the access token
access_token = token_response["access_token"]
refresh_token = token_response["refresh_token"]
# Create a test client with the token
auth_info = await mock_oauth_provider.load_access_token(access_token)
⋮----
# 6. Refresh the token
⋮----
new_token_response = response.json()
⋮----
# 7. Revoke the token
⋮----
# Verify that the token was revoked
⋮----
async def test_revoke_invalid_token(self, test_client, registered_client)
⋮----
"""Test revoking an invalid token."""
⋮----
# per RFC, this should return 200 even if the token is invalid
⋮----
async def test_revoke_with_malformed_token(self, test_client, registered_client)
⋮----
"""Test client registration with scopes that are not allowed."""
⋮----
"scope": "read write profile admin",  # 'admin' is not in valid_scopes
⋮----
# No scope specified
⋮----
# Verify client was registered successfully
⋮----
# Retrieve the client from the store to verify default scopes
registered_client = await mock_oauth_provider.get_client(
⋮----
# Check that default scopes were applied
⋮----
class TestAuthorizeEndpointErrors
⋮----
"""Test error handling in the OAuth authorization endpoint."""
⋮----
"""Test authorization endpoint with missing client_id.
        According to the OAuth2.0 spec, if client_id is missing, the server should
        inform the resource owner and NOT redirect.
        """
⋮----
# Missing client_id
⋮----
# Should NOT redirect, should show an error page
⋮----
# The response should include an error message about missing client_id
⋮----
"""Test authorization endpoint with invalid client_id.
        According to the OAuth2.0 spec, if client_id is invalid, the server should
        inform the resource owner and NOT redirect.
        """
⋮----
# The response should include an error message about invalid client_id
⋮----
"""Test authorization endpoint with missing redirect_uri.
        If client has only one registered redirect_uri, it can be omitted.
        """
⋮----
# Missing redirect_uri
⋮----
# Should redirect to the registered redirect_uri
⋮----
"""Test authorization endpoint with invalid redirect_uri.
        According to the OAuth2.0 spec, if redirect_uri is invalid or doesn't match,
        the server should inform the resource owner and NOT redirect.
        """
⋮----
# Non-matching URI
⋮----
# The response should include an error message about redirect_uri mismatch
⋮----
"""Test endpoint with missing redirect_uri with multiple registered URIs.
        If client has multiple registered redirect_uris, redirect_uri must be provided.
        """
⋮----
# Should NOT redirect, should return a 400 error
⋮----
# The response should include an error message about missing redirect_uri
⋮----
"""Test authorization endpoint with unsupported response_type.
        According to the OAuth2.0 spec, for other errors like unsupported_response_type,
        the server should redirect with error parameters.
        """
⋮----
"response_type": "token",  # Unsupported (we only support "code")
⋮----
# Should redirect with error parameters
⋮----
# State should be preserved
⋮----
"""Test authorization endpoint with missing response_type.
        Missing required parameter should result in invalid_request error.
        """
⋮----
# Missing response_type
⋮----
"""Test authorization endpoint with missing PKCE code_challenge.
        Missing PKCE parameters should result in invalid_request error.
        """
⋮----
# Missing code_challenge
⋮----
# using default URL
⋮----
"""Test authorization endpoint with invalid scope.
        Invalid scope should redirect with invalid_scope error.
        """

================
File: tests/server/test_context.py
================
class TestContextDeprecations
⋮----
def test_get_http_request_deprecation_warning(self)
⋮----
"""Test that using Context.get_http_request() raises a deprecation warning."""
# Create a mock FastMCP instance
mock_fastmcp = MagicMock()
context = Context(fastmcp=mock_fastmcp)
# Patch the dependency function to return a mock request
mock_request = MagicMock(spec=Request)
⋮----
# Check that the deprecation warning is raised
⋮----
request = context.get_http_request()
# Verify the function still works and returns the request
⋮----
def test_get_http_request_deprecation_message(self)
⋮----
"""Test that the deprecation warning has the correct message with guidance."""
⋮----
# Capture and check the specific warning message
⋮----
warning = w[0]
⋮----
@pytest.fixture
def context()
class TestParseModelPreferences
⋮----
def test_parse_model_preferences_string(self, context)
⋮----
mp = context._parse_model_preferences("claude-3-sonnet")
⋮----
def test_parse_model_preferences_list(self, context)
⋮----
mp = context._parse_model_preferences(["claude-3-sonnet", "claude"])
⋮----
def test_parse_model_preferences_object(self, context)
⋮----
obj = ModelPreferences(hints=[])
⋮----
def test_parse_model_preferences_invalid_type(self, context)
class TestSessionId
⋮----
def test_session_id_with_http_headers(self, context)
⋮----
"""Test that session_id returns the value from mcp-session-id header."""
mock_headers = {"mcp-session-id": "test-session-123"}
⋮----
def test_session_id_without_http_headers(self, context)
⋮----
"""Test that session_id returns None when no HTTP headers are available."""
⋮----
def test_session_id_with_missing_header(self, context)
⋮----
"""Test that session_id returns None when mcp-session-id header is missing."""
mock_headers = {"other-header": "value"}
⋮----
def test_session_id_with_empty_header(self, context)
⋮----
"""Test that session_id returns None when mcp-session-id header is empty."""
mock_headers = {"mcp-session-id": ""}
⋮----
assert context.session_id == ""  # Empty string is still returned as-is

================
File: tests/server/test_file_server.py
================
@pytest.fixture()
def test_dir(tmp_path_factory) -> Path
⋮----
"""Create a temporary directory with test files."""
tmp = tmp_path_factory.mktemp("test_files")
# Create test files
⋮----
@pytest.fixture
def mcp() -> FastMCP
⋮----
mcp = FastMCP()
⋮----
@pytest.fixture(autouse=True)
def resources(mcp: FastMCP, test_dir: Path) -> FastMCP
⋮----
@mcp.resource("dir://test_dir")
    def list_test_dir() -> list[str]
⋮----
"""List the files in the test directory"""
⋮----
@mcp.resource("file://test_dir/example.py")
    def read_example_py() -> str
⋮----
"""Read the example.py file"""
⋮----
@mcp.resource("file://test_dir/readme.md")
    def read_readme_md() -> str
⋮----
"""Read the readme.md file"""
⋮----
@mcp.resource("file://test_dir/config.json")
    def read_config_json() -> str
⋮----
"""Read the config.json file"""
⋮----
@pytest.fixture(autouse=True)
def tools(mcp: FastMCP, test_dir: Path) -> FastMCP
⋮----
@mcp.tool
    def delete_file(path: str) -> bool
⋮----
# ensure path is in test_dir
⋮----
async def test_list_resources(mcp: FastMCP)
⋮----
resources = await mcp._mcp_list_resources()
⋮----
async def test_read_resource_dir(mcp: FastMCP)
⋮----
res_iter = await mcp._mcp_read_resource("dir://test_dir")
res_list = list(res_iter)
⋮----
res = res_list[0]
⋮----
files = json.loads(res.content)
⋮----
async def test_read_resource_file(mcp: FastMCP)
⋮----
res_iter = await mcp._mcp_read_resource("file://test_dir/example.py")
⋮----
async def test_delete_file(mcp: FastMCP, test_dir: Path)
async def test_delete_file_and_check_resources(mcp: FastMCP, test_dir: Path)

================
File: tests/server/test_import_server.py
================
async def test_import_basic_functionality()
⋮----
"""Test that the import method properly imports tools and other resources."""
# Create main app and sub-app
main_app = FastMCP("MainApp")
sub_app = FastMCP("SubApp")
# Add a tool to the sub-app
⋮----
@sub_app.tool
    def sub_tool() -> str
# Import the sub-app to the main app
⋮----
# Verify the tool was imported with the prefix
⋮----
# Verify the original tool still exists in the sub-app
tool = await main_app._tool_manager.get_tool("sub_sub_tool")
⋮----
async def test_import_multiple_apps()
⋮----
"""Test importing multiple apps to a main app."""
# Create main app and multiple sub-apps
⋮----
weather_app = FastMCP("WeatherApp")
news_app = FastMCP("NewsApp")
# Add tools to each sub-app
⋮----
@weather_app.tool
    def get_forecast() -> str
⋮----
@news_app.tool
    def get_headlines() -> str
# Import both sub-apps to the main app
⋮----
# Verify tools were imported with the correct prefixes
⋮----
async def test_import_combines_tools()
⋮----
"""Test that importing preserves existing tools with the same prefix."""
# Create apps
⋮----
first_app = FastMCP("FirstApp")
second_app = FastMCP("SecondApp")
⋮----
@first_app.tool
    def first_tool() -> str
⋮----
@second_app.tool
    def second_tool() -> str
# Import first app
⋮----
# Import second app to same prefix
⋮----
# Verify second tool is there
⋮----
# Tools from both imports are combined
⋮----
async def test_import_with_resources()
⋮----
"""Test importing with resources."""
⋮----
data_app = FastMCP("DataApp")
# Add a resource to the data app
⋮----
@data_app.resource(uri="data://users")
    async def get_users()
# Import the data app
⋮----
# Verify the resource was imported with the prefix
⋮----
async def test_import_with_resource_templates()
⋮----
"""Test importing with resource templates."""
⋮----
user_app = FastMCP("UserApp")
# Add a resource template to the user app
⋮----
@user_app.resource(uri="users://{user_id}/profile")
    def get_user_profile(user_id: str) -> dict
# Import the user app
⋮----
# Verify the template was imported with the prefix
⋮----
async def test_import_with_prompts()
⋮----
"""Test importing with prompts."""
⋮----
assistant_app = FastMCP("AssistantApp")
# Add a prompt to the assistant app
⋮----
@assistant_app.prompt
    def greeting(name: str) -> str
# Import the assistant app
⋮----
# Verify the prompt was imported with the prefix
⋮----
async def test_import_multiple_resource_templates()
⋮----
"""Test importing multiple apps with resource templates."""
⋮----
# Add templates to each app
⋮----
@weather_app.resource(uri="weather://{city}")
    def get_weather(city: str) -> str
⋮----
@news_app.resource(uri="news://{category}")
    def get_news(category: str) -> str
# Import both apps
⋮----
# Verify templates were imported with correct prefixes
⋮----
async def test_import_multiple_prompts()
⋮----
"""Test importing multiple apps with prompts."""
⋮----
python_app = FastMCP("PythonApp")
sql_app = FastMCP("SQLApp")
# Add prompts to each app
⋮----
@python_app.prompt
    def review_python(code: str) -> str
⋮----
@sql_app.prompt
    def explain_sql(query: str) -> str
⋮----
# Verify prompts were imported with correct prefixes
⋮----
async def test_tool_custom_name_preserved_when_imported()
⋮----
"""Test that a tool's custom name is preserved when imported."""
⋮----
api_app = FastMCP("APIApp")
def fetch_data(query: str) -> str
⋮----
# Check that the tool is accessible by its prefixed name
tool = await main_app._tool_manager.get_tool("api_get_data")
⋮----
# Check that the function name is preserved
⋮----
async def test_call_imported_custom_named_tool()
⋮----
"""Test calling an imported tool with a custom name."""
⋮----
result = await client.call_tool("api_get_data", {"query": "test"})
⋮----
async def test_first_level_importing_with_custom_name()
⋮----
"""Test that a tool with a custom name is correctly imported at the first level."""
service_app = FastMCP("ServiceApp")
provider_app = FastMCP("ProviderApp")
def calculate_value(input: int) -> int
⋮----
# Tool is accessible in the service app with the first prefix
tool = await service_app._tool_manager.get_tool("provider_compute")
⋮----
async def test_nested_importing_preserves_prefixes()
⋮----
"""Test that importing a previously imported app preserves prefixes."""
⋮----
# Tool is accessible in the main app with both prefixes
tool = await main_app._tool_manager.get_tool("service_provider_compute")
⋮----
async def test_call_nested_imported_tool()
⋮----
"""Test calling a tool through multiple levels of importing."""
⋮----
result = await client.call_tool("service_provider_compute", {"input": 21})
⋮----
async def test_import_with_proxy_tools()
⋮----
"""
    Test importing with tools that have custom names (proxy tools).
    This tests that the tool's name doesn't change even though the registered
    name does, which is important because we need to forward that name to the
    proxy server correctly.
    """
⋮----
@api_app.tool
    def get_data(query: str) -> str
proxy_app = FastMCP.as_proxy(Client(api_app))
⋮----
async def test_import_with_proxy_prompts()
⋮----
"""
    Test importing with prompts that have custom keys.
    This tests that the prompt's name doesn't change even though the registered
    key does, which is important for correct rendering.
    """
⋮----
@api_app.prompt
    def greeting(name: str) -> str
⋮----
"""Example greeting prompt."""
⋮----
result = await client.get_prompt("api_greeting", {"name": "World"})
assert result.messages[0].content.text == "Hello, World from API!"  # type: ignore[attr-defined]
⋮----
async def test_import_with_proxy_resources()
⋮----
"""
    Test importing with resources that have custom keys.
    This tests that the resource's name doesn't change even though the registered
    key does, which is important for correct access.
    """
⋮----
# Create a resource in the API app
⋮----
@api_app.resource(uri="config://settings")
    def get_config()
⋮----
# Access the resource through the main app with the prefixed key
⋮----
result = await client.read_resource("config://api/settings")
content = json.loads(result[0].text)  # type: ignore[attr-defined]
⋮----
async def test_import_with_proxy_resource_templates()
⋮----
"""
    Test importing with resource templates that have custom keys.
    This tests that the template's name doesn't change even though the registered
    key does, which is important for correct instantiation.
    """
⋮----
# Create a resource template in the API app
⋮----
@api_app.resource(uri="user://{name}/{email}")
    def create_user(name: str, email: str)
⋮----
# Instantiate the template through the main app with the prefixed key
quoted_name = quote("John Doe", safe="")
quoted_email = quote("john@example.com", safe="")
⋮----
result = await client.read_resource(f"user://api/{quoted_name}/{quoted_email}")
⋮----
async def test_import_invalid_resource_prefix()
⋮----
# This test doesn't apply anymore with the new prefix format since we're not validating
# the protocol://prefix/path format
# Just import the server to maintain test coverage without deprecated parameters
⋮----
async def test_import_invalid_resource_separator()
⋮----
# This test is for maintaining coverage for importing with prefixes
# We no longer pass the deprecated resource_separator parameter
⋮----
async def test_import_with_no_prefix()
⋮----
"""Test importing a server without providing a prefix."""
⋮----
@sub_app.resource(uri="data://config")
    def sub_resource()
⋮----
@sub_app.resource(uri="users://{user_id}/info")
    def sub_template(user_id: str)
⋮----
@sub_app.prompt
    def sub_prompt() -> str
# Import without prefix
⋮----
# Verify all component types are accessible with original names
⋮----
# Test actual functionality through Client
⋮----
# Test tool
tool_result = await client.call_tool("sub_tool", {})
⋮----
# Test resource
resource_result = await client.read_resource("data://config")
assert resource_result[0].text == "Sub resource data"  # type: ignore[attr-defined]
# Test template
template_result = await client.read_resource("users://123/info")
assert template_result[0].text == "Sub template for user 123"  # type: ignore[attr-defined]
# Test prompt
prompt_result = await client.get_prompt("sub_prompt", {})
⋮----
assert prompt_result.messages[0].content.text == "Sub prompt content"  # type: ignore[attr-defined]
async def test_import_conflict_resolution_tools()
⋮----
"""Test that later imported tools overwrite earlier ones when names conflict."""
⋮----
@first_app.tool(name="shared_tool")
    def first_shared_tool() -> str
⋮----
@second_app.tool(name="shared_tool")
    def second_shared_tool() -> str
# Import both apps without prefix
⋮----
# The later imported server should win
tools = await client.list_tools()
tool_names = [t.name for t in tools]
⋮----
assert tool_names.count("shared_tool") == 1  # Should only appear once
result = await client.call_tool("shared_tool", {})
⋮----
async def test_import_conflict_resolution_resources()
⋮----
"""Test that later imported resources overwrite earlier ones when URIs conflict."""
⋮----
@first_app.resource(uri="shared://data")
    def first_resource()
⋮----
@second_app.resource(uri="shared://data")
    def second_resource()
⋮----
resources = await client.list_resources()
resource_uris = [str(r.uri) for r in resources]
⋮----
assert resource_uris.count("shared://data") == 1  # Should only appear once
result = await client.read_resource("shared://data")
assert result[0].text == "Second app data"  # type: ignore[attr-defined]
async def test_import_conflict_resolution_templates()
⋮----
"""Test that later imported templates overwrite earlier ones when URI templates conflict."""
⋮----
@first_app.resource(uri="users://{user_id}/profile")
    def first_template(user_id: str)
⋮----
@second_app.resource(uri="users://{user_id}/profile")
    def second_template(user_id: str)
⋮----
templates = await client.list_resource_templates()
template_uris = [t.uriTemplate for t in templates]
⋮----
)  # Should only appear once
result = await client.read_resource("users://123/profile")
assert result[0].text == "Second app user 123"  # type: ignore[attr-defined]
async def test_import_conflict_resolution_prompts()
⋮----
"""Test that later imported prompts overwrite earlier ones when names conflict."""
⋮----
@first_app.prompt(name="shared_prompt")
    def first_shared_prompt() -> str
⋮----
@second_app.prompt(name="shared_prompt")
    def second_shared_prompt() -> str
⋮----
prompts = await client.list_prompts()
prompt_names = [p.name for p in prompts]
⋮----
assert prompt_names.count("shared_prompt") == 1  # Should only appear once
result = await client.get_prompt("shared_prompt", {})
⋮----
assert result.messages[0].content.text == "Second app prompt"  # type: ignore[attr-defined]
async def test_import_conflict_resolution_with_prefix()
⋮----
"""Test that later imported components overwrite earlier ones when prefixed names conflict."""
⋮----
# Import both apps with same prefix
⋮----
assert tool_names.count("api_shared_tool") == 1  # Should only appear once
result = await client.call_tool("api_shared_tool", {})

================
File: tests/server/test_logging.py
================
class CustomLogFormatterForTest(logging.Formatter)
⋮----
def format(self, record: logging.LogRecord) -> str
⋮----
@pytest.fixture
def mcp_server() -> FastMCP
⋮----
"""Tests that FastMCP passes log_level to uvicorn.Config if no log_config is given."""
mock_server_instance = AsyncMock()
⋮----
serve_finished_event = anyio.Event()
⋮----
test_log_level = "warning"
server_task = asyncio.create_task(
⋮----
"""Tests that FastMCP passes log_config to uvicorn.Config and not log_level."""
⋮----
sample_log_config = {
⋮----
"""Tests log_config precedence if log_level is also passed to run_http_async."""
⋮----
explicit_log_level = "debug"

================
File: tests/server/test_mount.py
================
class TestBasicMount
⋮----
"""Test basic mounting functionality."""
async def test_mount_simple_server(self)
⋮----
"""Test mounting a simple server and accessing its tool."""
# Create main app and sub-app
main_app = FastMCP("MainApp")
sub_app = FastMCP("SubApp")
# Add a tool to the sub-app
⋮----
@sub_app.tool
        def sub_tool() -> str
# Mount the sub-app to the main app
⋮----
# Get tools from main app, should include sub_app's tools
tools = await main_app.get_tools()
⋮----
result = await client.call_tool("sub_sub_tool", {})
⋮----
async def test_mount_with_custom_separator(self)
⋮----
"""Test mounting with a custom tool separator (deprecated but still supported)."""
⋮----
@sub_app.tool
        def greet(name: str) -> str
# Mount without custom separator - custom separators are deprecated
⋮----
# Tool should be accessible with the default separator
⋮----
# Call the tool
⋮----
result = await client.call_tool("sub_greet", {"name": "World"})
⋮----
async def test_mount_invalid_resource_prefix(self)
⋮----
api_app = FastMCP("APIApp")
# This test doesn't apply anymore with the new prefix format
# just mount the server to maintain test coverage
⋮----
async def test_mount_invalid_resource_separator(self)
⋮----
# Mount without deprecated parameters
⋮----
@pytest.mark.parametrize("prefix", ["", None])
    async def test_mount_with_no_prefix(self, prefix)
⋮----
# Mount with empty prefix but without deprecated separators
⋮----
# With empty prefix, the tool should keep its original name
⋮----
async def test_mount_with_no_prefix_provided(self)
⋮----
"""Test mounting without providing a prefix at all."""
⋮----
# Mount without providing a prefix (should be None)
⋮----
# Without prefix, the tool should keep its original name
⋮----
# Call the tool to verify it works
⋮----
result = await client.call_tool("sub_tool", {})
⋮----
async def test_mount_tools_no_prefix(self)
⋮----
"""Test mounting a server with tools without prefix."""
⋮----
# Mount without prefix
⋮----
# Verify tool is accessible with original name
⋮----
# Test actual functionality
⋮----
tool_result = await client.call_tool("sub_tool", {})
⋮----
async def test_mount_resources_no_prefix(self)
⋮----
"""Test mounting a server with resources without prefix."""
⋮----
@sub_app.resource(uri="data://config")
        def sub_resource()
⋮----
# Verify resource is accessible with original URI
resources = await main_app.get_resources()
⋮----
resource_result = await client.read_resource("data://config")
assert resource_result[0].text == "Sub resource data"  # type: ignore[attr-defined]
async def test_mount_resource_templates_no_prefix(self)
⋮----
"""Test mounting a server with resource templates without prefix."""
⋮----
@sub_app.resource(uri="users://{user_id}/info")
        def sub_template(user_id: str)
⋮----
# Verify template is accessible with original URI template
templates = await main_app.get_resource_templates()
⋮----
template_result = await client.read_resource("users://123/info")
assert template_result[0].text == "Sub template for user 123"  # type: ignore[attr-defined]
async def test_mount_prompts_no_prefix(self)
⋮----
"""Test mounting a server with prompts without prefix."""
⋮----
@sub_app.prompt
        def sub_prompt() -> str
⋮----
# Verify prompt is accessible with original name
prompts = await main_app.get_prompts()
⋮----
prompt_result = await client.get_prompt("sub_prompt", {})
⋮----
class TestMultipleServerMount
⋮----
"""Test mounting multiple servers simultaneously."""
async def test_mount_multiple_servers(self)
⋮----
"""Test mounting multiple servers with different prefixes."""
⋮----
weather_app = FastMCP("WeatherApp")
news_app = FastMCP("NewsApp")
⋮----
@weather_app.tool
        def get_forecast() -> str
⋮----
@news_app.tool
        def get_headlines() -> str
# Mount both apps
⋮----
# Check both are accessible
⋮----
# Call tools from both mounted servers
⋮----
result1 = await client.call_tool("weather_get_forecast", {})
⋮----
result2 = await client.call_tool("news_get_headlines", {})
⋮----
async def test_mount_same_prefix(self)
⋮----
"""Test that mounting with the same prefix replaces the previous mount."""
⋮----
first_app = FastMCP("FirstApp")
second_app = FastMCP("SecondApp")
⋮----
@first_app.tool
        def first_tool() -> str
⋮----
@second_app.tool
        def second_tool() -> str
# Mount first app
⋮----
# Mount second app with same prefix
⋮----
# Both apps' tools should be accessible (new behavior)
⋮----
async def test_mount_with_unreachable_proxy_servers(self, caplog)
⋮----
"""Test graceful handling when multiple mounted servers fail to connect."""
⋮----
working_app = FastMCP("WorkingApp")
⋮----
@working_app.tool
        def working_tool() -> str
⋮----
@working_app.resource(uri="working://data")
        def working_resource()
⋮----
@working_app.prompt
        def working_prompt() -> str
# Mount the working server
⋮----
# Use an unreachable port
unreachable_client = Client(
# Create a proxy server that will fail to connect
unreachable_proxy = FastMCP.as_proxy(unreachable_client)
# Mount the unreachable proxy
⋮----
# All object types should work from working server despite unreachable proxy
⋮----
# Test tools
tools = await client.list_tools()
tool_names = [tool.name for tool in tools]
⋮----
# Test calling a tool
result = await client.call_tool("working_working_tool", {})
⋮----
# Test resources
resources = await client.list_resources()
resource_uris = [str(resource.uri) for resource in resources]
⋮----
# Test prompts
prompts = await client.list_prompts()
prompt_names = [prompt.name for prompt in prompts]
⋮----
# Verify that warnings were logged for the unreachable server
warning_messages = [
⋮----
class TestPrefixConflictResolution
⋮----
"""Test that later mounted servers win when there are conflicts."""
async def test_later_server_wins_tools_no_prefix(self)
⋮----
"""Test that later mounted server wins for tools when no prefix is used."""
⋮----
@first_app.tool(name="shared_tool")
        def first_shared_tool() -> str
⋮----
@second_app.tool(name="shared_tool")
        def second_shared_tool() -> str
# Mount both apps without prefix
⋮----
# Test that list_tools shows the tool from later server
⋮----
tool_names = [t.name for t in tools]
⋮----
assert tool_names.count("shared_tool") == 1  # Should only appear once
# Test that calling the tool uses the later server's implementation
result = await client.call_tool("shared_tool", {})
⋮----
async def test_later_server_wins_tools_same_prefix(self)
⋮----
"""Test that later mounted server wins for tools when same prefix is used."""
⋮----
# Mount both apps with same prefix
⋮----
assert tool_names.count("api_shared_tool") == 1  # Should only appear once
⋮----
result = await client.call_tool("api_shared_tool", {})
⋮----
async def test_later_server_wins_resources_no_prefix(self)
⋮----
"""Test that later mounted server wins for resources when no prefix is used."""
⋮----
@first_app.resource(uri="shared://data")
        def first_resource()
⋮----
@second_app.resource(uri="shared://data")
        def second_resource()
⋮----
# Test that list_resources shows the resource from later server
⋮----
resource_uris = [str(r.uri) for r in resources]
⋮----
assert resource_uris.count("shared://data") == 1  # Should only appear once
# Test that reading the resource uses the later server's implementation
result = await client.read_resource("shared://data")
assert result[0].text == "Second app data"  # type: ignore[attr-defined]
async def test_later_server_wins_resources_same_prefix(self)
⋮----
"""Test that later mounted server wins for resources when same prefix is used."""
⋮----
)  # Should only appear once
⋮----
result = await client.read_resource("shared://api/data")
⋮----
async def test_later_server_wins_resource_templates_no_prefix(self)
⋮----
"""Test that later mounted server wins for resource templates when no prefix is used."""
⋮----
@first_app.resource(uri="users://{user_id}/profile")
        def first_template(user_id: str)
⋮----
@second_app.resource(uri="users://{user_id}/profile")
        def second_template(user_id: str)
⋮----
# Test that list_resource_templates shows the template from later server
templates = await client.list_resource_templates()
template_uris = [t.uriTemplate for t in templates]
⋮----
result = await client.read_resource("users://123/profile")
assert result[0].text == "Second app user 123"  # type: ignore[attr-defined]
async def test_later_server_wins_resource_templates_same_prefix(self)
⋮----
"""Test that later mounted server wins for resource templates when same prefix is used."""
⋮----
result = await client.read_resource("users://api/123/profile")
⋮----
async def test_later_server_wins_prompts_no_prefix(self)
⋮----
"""Test that later mounted server wins for prompts when no prefix is used."""
⋮----
@first_app.prompt(name="shared_prompt")
        def first_shared_prompt() -> str
⋮----
@second_app.prompt(name="shared_prompt")
        def second_shared_prompt() -> str
⋮----
# Test that list_prompts shows the prompt from later server
⋮----
prompt_names = [p.name for p in prompts]
⋮----
assert prompt_names.count("shared_prompt") == 1  # Should only appear once
# Test that getting the prompt uses the later server's implementation
result = await client.get_prompt("shared_prompt", {})
⋮----
assert result.messages[0].content.text == "Second app prompt"  # type: ignore[attr-defined]
async def test_later_server_wins_prompts_same_prefix(self)
⋮----
"""Test that later mounted server wins for prompts when same prefix is used."""
⋮----
result = await client.get_prompt("api_shared_prompt", {})
⋮----
class TestDynamicChanges
⋮----
"""Test that changes to mounted servers are reflected dynamically."""
async def test_adding_tool_after_mounting(self)
⋮----
"""Test that tools added after mounting are accessible."""
⋮----
# Mount the sub-app before adding any tools
⋮----
# Initially, there should be no tools from sub_app
⋮----
# Add a tool to the sub-app after mounting
⋮----
@sub_app.tool
        def dynamic_tool() -> str
# The tool should be accessible through the main app
⋮----
# Call the dynamically added tool
⋮----
result = await client.call_tool("sub_dynamic_tool", {})
⋮----
async def test_removing_tool_after_mounting(self)
⋮----
"""Test that tools removed from mounted servers are no longer accessible."""
⋮----
@sub_app.tool
        def temp_tool() -> str
# Mount the sub-app
⋮----
# Initially, the tool should be accessible
⋮----
# Remove the tool from sub_app
⋮----
# The tool should no longer be accessible
# Refresh the cache by clearing it
⋮----
async def test_cache_expiration(self)
⋮----
main_app = FastMCP("MainApp", cache_expiration_seconds=2)
⋮----
@sub_app.tool
        def sub_tool()
⋮----
class TestResourcesAndTemplates
⋮----
"""Test mounting with resources and resource templates."""
async def test_mount_with_resources(self)
⋮----
"""Test mounting a server with resources."""
⋮----
data_app = FastMCP("DataApp")
⋮----
@data_app.resource(uri="data://users")
        async def get_users()
# Mount the data app
⋮----
# Resource should be accessible through main app
⋮----
# Check that resource can be accessed
⋮----
result = await client.read_resource("data://data/users")
assert json.loads(result[0].text) == ["user1", "user2"]  # type: ignore[attr-defined]
async def test_mount_with_resource_templates(self)
⋮----
"""Test mounting a server with resource templates."""
⋮----
user_app = FastMCP("UserApp")
⋮----
@user_app.resource(uri="users://{user_id}/profile")
        def get_user_profile(user_id: str) -> dict
# Mount the user app
⋮----
# Template should be accessible through main app
⋮----
# Check template instantiation
⋮----
profile = json.loads(result[0].text)  # type: ignore
⋮----
async def test_adding_resource_after_mounting(self)
⋮----
"""Test adding a resource after mounting."""
⋮----
# Mount the data app before adding resources
⋮----
# Add a resource after mounting
⋮----
@data_app.resource(uri="data://config")
        def get_config()
⋮----
# Check access to the resource
⋮----
result = await client.read_resource("data://data/config")
config = json.loads(result[0].text)  # type: ignore[attr-defined]
⋮----
class TestPrompts
⋮----
"""Test mounting with prompts."""
async def test_mount_with_prompts(self)
⋮----
"""Test mounting a server with prompts."""
⋮----
assistant_app = FastMCP("AssistantApp")
⋮----
@assistant_app.prompt
        def greeting(name: str) -> str
# Mount the assistant app
⋮----
# Prompt should be accessible through main app
⋮----
# Render the prompt
⋮----
result = await client.get_prompt("assistant_greeting", {"name": "World"})
⋮----
# The message should contain our greeting text
async def test_adding_prompt_after_mounting(self)
⋮----
"""Test adding a prompt after mounting."""
⋮----
# Mount the assistant app before adding prompts
⋮----
# Add a prompt after mounting
⋮----
@assistant_app.prompt
        def farewell(name: str) -> str
⋮----
result = await client.get_prompt("assistant_farewell", {"name": "World"})
⋮----
# The message should contain our farewell text
class TestProxyServer
⋮----
"""Test mounting a proxy server."""
async def test_mount_proxy_server(self)
⋮----
"""Test mounting a proxy server."""
# Create original server
original_server = FastMCP("OriginalServer")
⋮----
@original_server.tool
        def get_data(query: str) -> str
# Create proxy server
proxy_server = FastMCP.as_proxy(
# Mount proxy server
⋮----
# Tool should be accessible through main app
⋮----
result = await client.call_tool("proxy_get_data", {"query": "test"})
⋮----
async def test_dynamically_adding_to_proxied_server(self)
⋮----
"""Test that changes to the original server are reflected in the mounted proxy."""
⋮----
# Add a tool to the original server
⋮----
@original_server.tool
        def dynamic_data() -> str
# Tool should be accessible through main app via proxy
⋮----
result = await client.call_tool("proxy_dynamic_data", {})
⋮----
async def test_proxy_server_with_resources(self)
⋮----
"""Test mounting a proxy server with resources."""
⋮----
@original_server.resource(uri="config://settings")
        def get_config()
⋮----
result = await client.read_resource("config://proxy/settings")
⋮----
async def test_proxy_server_with_prompts(self)
⋮----
"""Test mounting a proxy server with prompts."""
⋮----
@original_server.prompt
        def welcome(name: str) -> str
⋮----
result = await client.get_prompt("proxy_welcome", {"name": "World"})
⋮----
# The message should contain our welcome text
class TestAsProxyKwarg
⋮----
"""Test the as_proxy kwarg."""
async def test_as_proxy_defaults_false(self)
⋮----
mcp = FastMCP("Main")
sub = FastMCP("Sub")
⋮----
async def test_as_proxy_false(self)
async def test_as_proxy_true(self)
async def test_as_proxy_defaults_true_if_lifespan(self)
⋮----
@asynccontextmanager
        async def lifespan(mcp: FastMCP)
⋮----
sub = FastMCP("Sub", lifespan=lifespan)
⋮----
async def test_as_proxy_ignored_for_proxy_mounts_default(self)
⋮----
sub_proxy = FastMCP.as_proxy(Client(transport=FastMCPTransport(sub)))
⋮----
async def test_as_proxy_ignored_for_proxy_mounts_false(self)
async def test_as_proxy_ignored_for_proxy_mounts_true(self)
async def test_as_proxy_mounts_still_have_live_link(self)
⋮----
@sub.tool
        def hello()
⋮----
async def test_sub_lifespan_is_executed(self)
⋮----
lifespan_check = []
⋮----
# in the present implementation the sub server will be invoked 3 times
# to call its tool

================
File: tests/server/test_proxy.py
================
USERS = [
⋮----
@pytest.fixture
def fastmcp_server()
⋮----
server = FastMCP("TestServer")
# --- Tools ---
⋮----
@server.tool
    def greet(name: str) -> str
⋮----
"""Greet someone by name."""
⋮----
@server.tool
    def tool_without_description() -> str
⋮----
@server.tool
    def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.tool
    def error_tool()
⋮----
"""This tool always raises an error."""
⋮----
# --- Resources ---
⋮----
@server.resource(uri="resource://wave")
    def wave() -> str
⋮----
@server.resource(uri="data://users")
    async def get_users() -> list[dict[str, Any]]
⋮----
@server.resource(uri="data://user/{user_id}")
    async def get_user(user_id: str) -> dict[str, Any] | None
# --- Prompts ---
⋮----
@server.prompt
    def welcome(name: str) -> str
⋮----
@pytest.fixture
async def proxy_server(fastmcp_server)
⋮----
"""Fixture that creates a FastMCP proxy server."""
⋮----
async def test_create_proxy(fastmcp_server)
⋮----
"""Test that the proxy server properly forwards requests to the original server."""
# Create a client
client = Client(transport=FastMCPTransport(fastmcp_server))
server = FastMCPProxy.as_proxy(client)
⋮----
async def test_as_proxy_with_server(fastmcp_server)
⋮----
"""FastMCP.as_proxy should accept a FastMCP instance."""
proxy = FastMCP.as_proxy(fastmcp_server)
⋮----
result = await client.call_tool("greet", {"name": "Test"})
⋮----
async def test_as_proxy_with_transport(fastmcp_server)
⋮----
"""FastMCP.as_proxy should accept a ClientTransport."""
proxy = FastMCP.as_proxy(FastMCPTransport(fastmcp_server))
⋮----
def test_as_proxy_with_url()
⋮----
"""FastMCP.as_proxy should accept a URL without connecting."""
proxy = FastMCP.as_proxy("http://example.com/mcp/")
⋮----
class TestTools
⋮----
async def test_get_tools(self, proxy_server)
⋮----
tools = await proxy_server.get_tools()
⋮----
async def test_tool_without_description(self, proxy_server)
async def test_list_tools_same_as_original(self, fastmcp_server, proxy_server)
⋮----
result = await fastmcp_server._mcp_call_tool("greet", {"name": "Alice"})
proxy_result = await proxy_server._mcp_call_tool("greet", {"name": "Alice"})
⋮----
async def test_call_tool_calls_tool(self, proxy_server)
⋮----
proxy_result = await client.call_tool("add", {"a": 1, "b": 2})
⋮----
async def test_error_tool_raises_error(self, proxy_server)
async def test_proxy_can_overwrite_proxied_tool(self, proxy_server)
⋮----
"""
        Test that a tool defined on the proxy can overwrite the proxied tool with the same name.
        """
⋮----
@proxy_server.tool
        def greet(name: str, extra: str = "extra") -> str
⋮----
result = await client.call_tool("greet", {"name": "Marvin", "extra": "abc"})
⋮----
async def test_proxy_errors_if_overwritten_tool_is_disabled(self, proxy_server)
⋮----
"""
        Test that a tool defined on the proxy is not listed if it is disabled,
        and it doesn't fall back to the proxied tool with the same name
        """
⋮----
@proxy_server.tool(enabled=False)
        def greet(name: str, extra: str = "extra") -> str
⋮----
async def test_proxy_can_list_overwritten_tool(self, proxy_server)
⋮----
"""
        Test that a tool defined on the proxy is listed instead of the proxied tool
        """
⋮----
tools = await client.list_tools()
greet_tool = next(t for t in tools if t.name == "greet")
⋮----
async def test_proxy_can_list_overwritten_tool_if_disabled(self, proxy_server)
class TestResources
⋮----
async def test_get_resources(self, proxy_server)
⋮----
resources = await proxy_server.get_resources()
⋮----
async def test_list_resources_same_as_original(self, fastmcp_server, proxy_server)
async def test_read_resource(self, proxy_server: FastMCPProxy)
⋮----
result = await client.read_resource("resource://wave")
assert result[0].text == "👋"  # type: ignore[attr-defined]
async def test_read_resource_same_as_original(self, fastmcp_server, proxy_server)
⋮----
proxy_result = await client.read_resource("resource://wave")
⋮----
async def test_read_json_resource(self, proxy_server: FastMCPProxy)
⋮----
result = await client.read_resource("data://users")
assert json.loads(result[0].text) == USERS  # type: ignore[attr-defined]
async def test_read_resource_returns_none_if_not_found(self, proxy_server)
async def test_proxy_can_overwrite_proxied_resource(self, proxy_server)
⋮----
"""
        Test that a resource defined on the proxy can overwrite the proxied resource with the same URI.
        """
⋮----
@proxy_server.resource(uri="resource://wave")
        def overwritten_wave() -> str
⋮----
assert result[0].text == "Overwritten wave! 🌊"  # type: ignore[attr-defined]
async def test_proxy_errors_if_overwritten_resource_is_disabled(self, proxy_server)
⋮----
"""
        Test that a resource defined on the proxy is not accessible if it is disabled,
        and it doesn't fall back to the proxied resource with the same URI
        """
⋮----
@proxy_server.resource(uri="resource://wave", enabled=False)
        def overwritten_wave() -> str
⋮----
async def test_proxy_can_list_overwritten_resource(self, proxy_server)
⋮----
"""
        Test that a resource defined on the proxy is listed instead of the proxied resource
        """
⋮----
@proxy_server.resource(uri="resource://wave", name="overwritten_wave")
        def overwritten_wave() -> str
⋮----
resources = await client.list_resources()
wave_resource = next(
⋮----
async def test_proxy_can_list_overwritten_resource_if_disabled(self, proxy_server)
⋮----
"""
        Test that a resource defined on the proxy is not listed if it is disabled,
        and it doesn't fall back to the proxied resource with the same URI
        """
⋮----
wave_resources = [r for r in resources if str(r.uri) == "resource://wave"]
⋮----
class TestResourceTemplates
⋮----
async def test_get_resource_templates(self, proxy_server)
⋮----
templates = await proxy_server.get_resource_templates()
⋮----
result = await fastmcp_server._mcp_list_resource_templates()
proxy_result = await proxy_server._mcp_list_resource_templates()
⋮----
@pytest.mark.parametrize("id", [1, 2, 3])
    async def test_read_resource_template(self, proxy_server: FastMCPProxy, id: int)
⋮----
result = await client.read_resource(f"data://user/{id}")
assert json.loads(result[0].text) == USERS[id - 1]  # type: ignore[attr-defined]
⋮----
result = await client.read_resource("data://user/1")
⋮----
proxy_result = await client.read_resource("data://user/1")
⋮----
async def test_proxy_can_overwrite_proxied_resource_template(self, proxy_server)
⋮----
"""
        Test that a resource template defined on the proxy can overwrite the proxied template with the same URI template.
        """
⋮----
@proxy_server.resource(uri="data://user/{user_id}", name="overwritten_get_user")
        def overwritten_get_user(user_id: str) -> dict[str, Any]
⋮----
user_data = json.loads(result[0].text)  # type: ignore[attr-defined]
⋮----
"""
        Test that a resource template defined on the proxy is not accessible if it is disabled,
        and it doesn't fall back to the proxied template with the same URI template
        """
⋮----
@proxy_server.resource(uri="data://user/{user_id}", enabled=False)
        def overwritten_get_user(user_id: str) -> dict[str, Any]
⋮----
async def test_proxy_can_list_overwritten_resource_template(self, proxy_server)
⋮----
"""
        Test that a resource template defined on the proxy is listed instead of the proxied template
        """
⋮----
templates = await client.list_resource_templates()
user_template = next(
⋮----
"""
        Test that a resource template defined on the proxy is not listed if it is disabled,
        and it doesn't fall back to the proxied template with the same URI template
        """
⋮----
user_templates = [
⋮----
class TestPrompts
⋮----
async def test_get_prompts_server_method(self, proxy_server: FastMCPProxy)
⋮----
prompts = await proxy_server.get_prompts()
⋮----
async def test_list_prompts_same_as_original(self, fastmcp_server, proxy_server)
⋮----
result = await client.list_prompts()
⋮----
proxy_result = await client.list_prompts()
⋮----
result = await client.get_prompt("welcome", {"name": "Alice"})
⋮----
proxy_result = await client.get_prompt("welcome", {"name": "Alice"})
⋮----
async def test_render_prompt_calls_prompt(self, proxy_server)
⋮----
assert result.messages[0].content.text == "Welcome to FastMCP, Alice!"  # type: ignore[attr-defined]
async def test_proxy_can_overwrite_proxied_prompt(self, proxy_server)
⋮----
"""
        Test that a prompt defined on the proxy can overwrite the proxied prompt with the same name.
        """
⋮----
@proxy_server.prompt
        def welcome(name: str, extra: str = "friend") -> str
⋮----
result = await client.get_prompt(
⋮----
result.messages[0].content.text  # type: ignore[attr-defined]
⋮----
async def test_proxy_errors_if_overwritten_prompt_is_disabled(self, proxy_server)
⋮----
"""
        Test that a prompt defined on the proxy is not accessible if it is disabled,
        and it doesn't fall back to the proxied prompt with the same name
        """
⋮----
@proxy_server.prompt(enabled=False)
        def welcome(name: str, extra: str = "friend") -> str
⋮----
async def test_proxy_can_list_overwritten_prompt(self, proxy_server)
⋮----
"""
        Test that a prompt defined on the proxy is listed instead of the proxied prompt
        """
⋮----
prompts = await client.list_prompts()
welcome_prompt = next(p for p in prompts if p.name == "welcome")
# Check that the overwritten prompt has the additional 'extra' parameter
param_names = [arg.name for arg in welcome_prompt.arguments or []]
⋮----
async def test_proxy_can_list_overwritten_prompt_if_disabled(self, proxy_server)
⋮----
"""
        Test that a prompt defined on the proxy is not listed if it is disabled,
        and it doesn't fall back to the proxied prompt with the same name
        """
⋮----
welcome_prompts = [p for p in prompts if p.name == "welcome"]
⋮----
results = {}
async def get_and_store(name, coro)

================
File: tests/server/test_resource_prefix_formats.py
================
"""Tests for different resource prefix formats in server mounting and importing."""
⋮----
async def test_resource_prefix_format_in_constructor()
⋮----
"""Test that the resource_prefix_format parameter is respected in the constructor."""
server_path = FastMCP("PathFormat", resource_prefix_format="path")
server_protocol = FastMCP("ProtocolFormat", resource_prefix_format="protocol")
# Check that the format is stored correctly
⋮----
# Register resources
⋮----
@server_path.resource("resource://test")
    def get_test_path()
⋮----
@server_protocol.resource("resource://test")
    def get_test_protocol()
# Create mount servers
main_server_path = FastMCP("MainPath", resource_prefix_format="path")
main_server_protocol = FastMCP("MainProtocol", resource_prefix_format="protocol")
# Mount the servers
⋮----
# Check that the resources are prefixed correctly
path_resources = await main_server_path.get_resources()
protocol_resources = await main_server_protocol.get_resources()
# Path format should be resource://sub/test
⋮----
# Protocol format should be sub+resource://test
⋮----
async def test_resource_prefix_format_in_import_server()
⋮----
"""Test that the resource_prefix_format parameter is respected in import_server."""
server = FastMCP("TestServer")
⋮----
@server.resource("resource://test")
    def get_test()
# Import with path format
⋮----
# Import with protocol format
⋮----
path_resources = await main_server_path._resource_manager.get_resources()
protocol_resources = await main_server_protocol._resource_manager.get_resources()

================
File: tests/server/test_run_server.py
================
# from pathlib import Path
# from typing import TYPE_CHECKING, Any
# import pytest
# import fastmcp
# from fastmcp import FastMCP
# if TYPE_CHECKING:
#     pass
# USERS = [
#     {"id": "1", "name": "Alice", "active": True},
#     {"id": "2", "name": "Bob", "active": True},
#     {"id": "3", "name": "Charlie", "active": False},
# ]
# @pytest.fixture
# def fastmcp_server():
#     server = FastMCP("TestServer")
#     # --- Tools ---
#     @server.tool
#     def greet(name: str) -> str:
#         """Greet someone by name."""
#         return f"Hello, {name}!"
⋮----
#     def add(a: int, b: int) -> int:
#         """Add two numbers together."""
#         return a + b
⋮----
#     def error_tool():
#         """This tool always raises an error."""
#         raise ValueError("This is a test error")
#     # --- Resources ---
#     @server.resource(uri="resource://wave")
#     def wave() -> str:
#         return "👋"
#     @server.resource(uri="data://users")
#     async def get_users() -> list[dict[str, Any]]:
#         return USERS
#     @server.resource(uri="data://user/{user_id}")
#     async def get_user(user_id: str) -> dict[str, Any] | None:
#         return next((user for user in USERS if user["id"] == user_id), None)
#     # --- Prompts ---
#     @server.prompt
#     def welcome(name: str) -> str:
#         return f"Welcome to FastMCP, {name}!"
#     return server
⋮----
# async def stdio_client():
#     # Find the stdio.py script path
#     base_dir = Path(__file__).parent
#     stdio_script = base_dir / "test_servers" / "stdio.py"
#     if not stdio_script.exists():
#         raise FileNotFoundError(f"Could not find stdio.py script at {stdio_script}")
#     client = fastmcp.Client(
#         transport=fastmcp.client.transports.StdioTransport(
#             command="python",
#             args=[str(stdio_script)],
#         )
#     )
#     async with client:
#         print("READY")
#         yield client
#         print("DONE")
# class TestRunServerStdio:
#     async def test_run_server_stdio(
#         self, fastmcp_server: FastMCP, stdio_client: fastmcp.Client
#     ):
#         print("TEST")
#         tools = await stdio_client.list_tools()
#         print("TEST 2")
#         assert tools == 1
# class TestRunServerSSE:
#
#     async def test_run_server_sse(self, fastmcp_server: FastMCP):
#         pass

================
File: tests/server/test_server_interactions.py
================
class PersonTypedDict(TypedDict)
⋮----
name: str
age: int
class PersonModel(BaseModel)
⋮----
@dataclass
class PersonDataclass
⋮----
@pytest.fixture
def tool_server()
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
    def add(x: int, y: int) -> int
⋮----
@mcp.tool
    def list_tool() -> list[str | int]
⋮----
@mcp.tool
    def error_tool() -> None
⋮----
@mcp.tool
    def image_tool(path: str) -> Image
⋮----
@mcp.tool
    def audio_tool(path: str) -> Audio
⋮----
@mcp.tool
    def file_tool(path: str) -> File
⋮----
@mcp.tool
    def mixed_content_tool() -> list[TextContent | ImageContent | EmbeddedResource]
⋮----
@mcp.tool(output_schema=None)
    def mixed_list_fn(image_path: str) -> list
⋮----
@mcp.tool(output_schema=None)
    def mixed_audio_list_fn(audio_path: str) -> list
⋮----
@mcp.tool(output_schema=None)
    def mixed_file_list_fn(file_path: str) -> list
⋮----
@mcp.tool
    def file_text_tool() -> File
⋮----
# Return a File with text data and text/plain format
⋮----
class TestTools
⋮----
async def test_add_tool_exists(self, tool_server: FastMCP)
⋮----
tools = await client.list_tools()
⋮----
async def test_list_tools(self, tool_server: FastMCP)
async def test_call_tool_mcp(self, tool_server: FastMCP)
⋮----
result = await client.call_tool_mcp("add", {"x": 1, "y": 2})
assert result.content[0].text == "3"  # type: ignore[attr-defined]
⋮----
async def test_call_tool(self, tool_server: FastMCP)
⋮----
result = await client.call_tool("add", {"x": 1, "y": 2})
⋮----
async def test_call_tool_error(self, tool_server: FastMCP)
async def test_call_tool_error_as_client_raw(self)
⋮----
"""Test raising and catching errors from a tool."""
⋮----
client = Client(transport=FastMCPTransport(mcp))
⋮----
@mcp.tool
        def error_tool()
⋮----
async def test_tool_returns_list(self, tool_server: FastMCP)
⋮----
result = await client.call_tool("list_tool", {})
assert result.content[0].text == '[\n  "x",\n  2\n]'  # type: ignore[attr-defined]
⋮----
async def test_file_text_tool(self, tool_server: FastMCP)
⋮----
result = await client.call_tool("file_text_tool", {})
⋮----
embedded = result.content[0]
⋮----
resource = embedded.resource
⋮----
class TestToolTags
⋮----
def create_server(self, include_tags=None, exclude_tags=None)
⋮----
mcp = FastMCP(include_tags=include_tags, exclude_tags=exclude_tags)
⋮----
@mcp.tool(tags={"a", "b"})
        def tool_1() -> int
⋮----
@mcp.tool(tags={"b", "c"})
        def tool_2() -> int
⋮----
async def test_include_tags_all_tools(self)
⋮----
mcp = self.create_server(include_tags={"a", "b"})
⋮----
async def test_include_tags_some_tools(self)
⋮----
mcp = self.create_server(include_tags={"a", "z"})
⋮----
async def test_exclude_tags_all_tools(self)
⋮----
mcp = self.create_server(exclude_tags={"a", "b"})
⋮----
async def test_exclude_tags_some_tools(self)
⋮----
mcp = self.create_server(exclude_tags={"a", "z"})
⋮----
async def test_exclude_precedence(self)
⋮----
mcp = self.create_server(exclude_tags={"a"}, include_tags={"b"})
⋮----
async def test_call_included_tool(self)
⋮----
mcp = self.create_server(include_tags={"a"})
⋮----
result_1 = await client.call_tool("tool_1", {})
⋮----
async def test_call_excluded_tool(self)
⋮----
mcp = self.create_server(exclude_tags={"a"})
⋮----
result_2 = await client.call_tool("tool_2", {})
⋮----
class TestToolReturnTypes
⋮----
async def test_string(self)
⋮----
@mcp.tool
        def string_tool() -> str
⋮----
result = await client.call_tool("string_tool", {})
⋮----
async def test_bytes(self, tmp_path: Path)
⋮----
@mcp.tool
        def bytes_tool() -> bytes
⋮----
result = await client.call_tool("bytes_tool", {})
⋮----
async def test_uuid(self)
⋮----
test_uuid = uuid.uuid4()
⋮----
@mcp.tool
        def uuid_tool() -> uuid.UUID
⋮----
result = await client.call_tool("uuid_tool", {})
⋮----
async def test_path(self)
⋮----
test_path = Path("/tmp/test.txt")
⋮----
@mcp.tool
        def path_tool() -> Path
⋮----
result = await client.call_tool("path_tool", {})
⋮----
async def test_datetime(self)
⋮----
dt = datetime.datetime(2025, 4, 25, 1, 2, 3)
⋮----
@mcp.tool
        def datetime_tool() -> datetime.datetime
⋮----
result = await client.call_tool("datetime_tool", {})
⋮----
async def test_image(self, tmp_path: Path)
⋮----
@mcp.tool
        def image_tool(path: str) -> Image
# Create a test image
image_path = tmp_path / "test.png"
⋮----
result = await client.call_tool("image_tool", {"path": str(image_path)})
⋮----
content = result.content[0]
⋮----
# Verify base64 encoding
decoded = base64.b64decode(content.data)
⋮----
async def test_audio(self, tmp_path: Path)
⋮----
@mcp.tool
        def audio_tool(path: str) -> Audio
# Create a test audio file
audio_path = tmp_path / "test.wav"
⋮----
result = await client.call_tool("audio_tool", {"path": str(audio_path)})
⋮----
async def test_file(self, tmp_path: Path)
⋮----
@mcp.tool
        def file_tool(path: str) -> File
# Create a test file
file_path = tmp_path / "test.bin"
⋮----
result = await client.call_tool("file_tool", {"path": str(file_path)})
⋮----
resource = content.resource
⋮----
blob_data = getattr(resource, "blob")
decoded = base64.b64decode(blob_data)
⋮----
# Verify URI points to the file
⋮----
async def test_tool_mixed_content(self, tool_server: FastMCP)
⋮----
result = await client.call_tool("mixed_content_tool", {})
⋮----
content1 = result.content[0]
content2 = result.content[1]
content3 = result.content[2]
⋮----
resource = content3.resource
⋮----
"""Test that lists containing Image objects and other types are handled
        correctly. Note that the non-MCP content will be grouped together."""
⋮----
result = await client.call_tool(
⋮----
# Check text conversion
⋮----
# Check image conversion
⋮----
# Check direct TextContent
⋮----
"""Test that lists containing Audio objects and other types are handled
        correctly. Note that the non-MCP content will be grouped together."""
⋮----
# Check audio conversion
⋮----
"""Test that lists containing File objects and other types are handled
        correctly. Note that the non-MCP content will be grouped together."""
⋮----
# Check file conversion
⋮----
resource = content2.resource
⋮----
class TestToolParameters
⋮----
async def test_parameter_descriptions_with_field_annotations(self)
⋮----
mcp = FastMCP("Test Server")
⋮----
"""A greeting tool"""
⋮----
tool = tools[0]
# Check that parameter descriptions are present in the schema
properties = tool.inputSchema["properties"]
⋮----
async def test_parameter_descriptions_with_field_defaults(self)
async def test_tool_with_bytes_input(self)
⋮----
@mcp.tool
        def process_image(image: bytes) -> Image
⋮----
async def test_tool_with_invalid_input(self)
⋮----
@mcp.tool
        def my_tool(x: int) -> int
⋮----
async def test_tool_int_coercion(self)
⋮----
"""Test that invalid int input raises validation error."""
⋮----
@mcp.tool
        def add_one(x: int) -> int
⋮----
# String input should raise validation error (no coercion)
⋮----
async def test_tool_bool_coercion(self)
⋮----
"""Test that invalid bool input raises validation error."""
⋮----
@mcp.tool
        def toggle(flag: bool) -> bool
⋮----
async def test_annotated_field_validation(self)
⋮----
@mcp.tool
        def analyze(x: Annotated[int, Field(ge=1)]) -> None
⋮----
async def test_default_field_validation(self)
⋮----
@mcp.tool
        def analyze(x: int = Field(ge=1)) -> None
⋮----
async def test_default_field_is_still_required_if_no_default_specified(self)
⋮----
@mcp.tool
        def analyze(x: int = Field()) -> None
⋮----
async def test_literal_type_validation_error(self)
⋮----
@mcp.tool
        def analyze(x: Literal["a", "b"]) -> None
⋮----
async def test_literal_type_validation_success(self)
⋮----
@mcp.tool
        def analyze(x: Literal["a", "b"]) -> str
⋮----
result = await client.call_tool("analyze", {"x": "a"})
⋮----
async def test_enum_type_validation_error(self)
⋮----
class MyEnum(Enum)
⋮----
RED = "red"
GREEN = "green"
BLUE = "blue"
⋮----
@mcp.tool
        def analyze(x: MyEnum) -> str
⋮----
async def test_enum_type_validation_success(self)
⋮----
result = await client.call_tool("analyze", {"x": "red"})
⋮----
async def test_union_type_validation(self)
⋮----
@mcp.tool
        def analyze(x: int | float) -> str
⋮----
result = await client.call_tool("analyze", {"x": 1})
⋮----
result = await client.call_tool("analyze", {"x": 1.0})
⋮----
async def test_path_type(self)
⋮----
@mcp.tool
        def send_path(path: Path) -> str
# Use a platform-independent path
test_path = Path("tmp") / "test.txt"
⋮----
result = await client.call_tool("send_path", {"path": str(test_path)})
⋮----
async def test_path_type_error(self)
async def test_uuid_type(self)
⋮----
@mcp.tool
        def send_uuid(x: uuid.UUID) -> str
⋮----
result = await client.call_tool("send_uuid", {"x": test_uuid})
⋮----
async def test_uuid_type_error(self)
async def test_datetime_type(self)
⋮----
@mcp.tool
        def send_datetime(x: datetime.datetime) -> str
⋮----
result = await client.call_tool("send_datetime", {"x": dt})
⋮----
async def test_datetime_type_parse_string(self)
async def test_datetime_type_error(self)
async def test_date_type(self)
⋮----
@mcp.tool
        def send_date(x: datetime.date) -> str
⋮----
result = await client.call_tool("send_date", {"x": datetime.date.today()})
⋮----
async def test_date_type_parse_string(self)
⋮----
result = await client.call_tool("send_date", {"x": "2021-01-01"})
⋮----
async def test_timedelta_type(self)
⋮----
@mcp.tool
        def send_timedelta(x: datetime.timedelta) -> str
⋮----
async def test_timedelta_type_parse_int(self)
⋮----
"""Test that invalid timedelta input raises validation error."""
⋮----
# Int input should raise validation error (no conversion)
⋮----
class TestToolOutputSchema
⋮----
@pytest.mark.parametrize("annotation", [str, int, float, bool, list, AnyUrl])
    async def test_simple_output_schema(self, annotation)
⋮----
@mcp.tool
        def f() -> annotation:  # type: ignore
⋮----
type_schema = TypeAdapter(annotation).json_schema()
# this line will fail until MCP adds output schemas!!
⋮----
async def test_structured_output_schema(self, annotation)
⋮----
@mcp.tool
        def f() -> annotation:  # type: ignore[valid-type]
⋮----
async def test_disabled_output_schema_no_structured_content(self)
⋮----
@mcp.tool(output_schema=None)
        def f() -> int
⋮----
result = await client.call_tool("f", {})
assert result.content[0].text == "42"  # type: ignore[attr-defined]
⋮----
async def test_manual_structured_content(self)
⋮----
@mcp.tool
        def f() -> ToolResult
⋮----
assert result.content[0].text == "Hello, world!"  # type: ignore[attr-defined]
⋮----
async def test_output_schema_false_full_handshake(self)
⋮----
"""Test that output_schema=False works through full client/server
        handshake. We test this by returning a scalar, which requires an output
        schema to serialize."""
⋮----
@mcp.tool(output_schema=False)  # type: ignore[arg-type]
@mcp.tool(output_schema=False)  # type: ignore[arg-type]
        def simple_tool() -> int
⋮----
# List tools and verify output schema is None
⋮----
tool = next(t for t in tools if t.name == "simple_tool")
⋮----
# Call tool and verify no structured content
result = await client.call_tool("simple_tool", {})
⋮----
async def test_output_schema_explicit_object_full_handshake(self)
⋮----
"""Test explicit object output schema through full client/server handshake."""
⋮----
def explicit_tool() -> dict[str, Any]
⋮----
# List tools and verify exact schema is preserved
⋮----
tool = next(t for t in tools if t.name == "explicit_tool")
expected_schema = {
⋮----
# Call tool and verify structured content matches return value directly
result = await client.call_tool("explicit_tool", {})
⋮----
# Client deserializes according to schema, so check fields
assert result.data.greeting == "Hello"  # type: ignore[attr-defined]
assert result.data.count == 42  # type: ignore[attr-defined]
async def test_output_schema_wrapped_primitive_full_handshake(self)
⋮----
"""Test wrapped primitive output schema through full client/server handshake."""
⋮----
@mcp.tool
        def primitive_tool() -> str
⋮----
# List tools and verify schema shows wrapped structure
⋮----
tool = next(t for t in tools if t.name == "primitive_tool")
⋮----
# Call tool and verify structured content is wrapped
result = await client.call_tool("primitive_tool", {})
⋮----
assert result.data == "Hello, primitives!"  # Client unwraps for convenience
async def test_output_schema_complex_type_full_handshake(self)
⋮----
"""Test complex type output schema through full client/server handshake."""
⋮----
@mcp.tool
        def complex_tool() -> list[dict[str, int]]
⋮----
# List tools and verify schema shows wrapped array
⋮----
tool = next(t for t in tools if t.name == "complex_tool")
expected_inner_schema = TypeAdapter(list[dict[str, int]]).json_schema()
⋮----
result = await client.call_tool("complex_tool", {})
expected_data = [{"a": 1, "b": 2}, {"c": 3, "d": 4}]
⋮----
# Client deserializes - just verify we got data back
⋮----
async def test_output_schema_dataclass_full_handshake(self)
⋮----
"""Test dataclass output schema through full client/server handshake."""
⋮----
@dataclass
        class User
⋮----
@mcp.tool
        def dataclass_tool() -> User
⋮----
# List tools and verify schema is object type (not wrapped)
⋮----
tool = next(t for t in tools if t.name == "dataclass_tool")
expected_schema = TypeAdapter(User).json_schema()
⋮----
# Call tool and verify structured content is direct
result = await client.call_tool("dataclass_tool", {})
⋮----
# Client deserializes according to schema
assert result.data.name == "Alice"  # type: ignore[attr-defined]
assert result.data.age == 30  # type: ignore[attr-defined]
async def test_output_schema_mixed_content_types(self)
⋮----
"""Test tools with mixed content and output schemas."""
⋮----
@mcp.tool
        def mixed_output() -> list[Any]
⋮----
# Return mixed content that includes MCP types and regular data
⋮----
result = await client.call_tool("mixed_output", {})
# Should have multiple content blocks
⋮----
# Should have structured output with wrapped result
expected_data = [
⋮----
async def test_output_schema_serialization_edge_cases(self)
⋮----
"""Test edge cases in output schema serialization."""
⋮----
@mcp.tool
        def edge_case_tool() -> tuple[int, str]
⋮----
# Verify tuple gets proper schema
⋮----
tool = next(t for t in tools if t.name == "edge_case_tool")
# Tuples should be wrapped since they're not object type
⋮----
result = await client.call_tool("edge_case_tool", {})
# Should be wrapped with result key
⋮----
class TestToolContextInjection
⋮----
"""Test context injection in tools."""
async def test_context_detection(self)
⋮----
"""Test that context parameters are properly detected."""
⋮----
@mcp.tool
        def tool_with_context(x: int, ctx: Context) -> str
⋮----
async def test_context_injection(self)
⋮----
"""Test that context is properly injected into tool calls."""
⋮----
result = await client.call_tool("tool_with_context", {"x": 42})
⋮----
async def test_async_context(self)
⋮----
"""Test that context works in async functions."""
⋮----
@mcp.tool
        async def async_tool(x: int, ctx: Context) -> str
⋮----
result = await client.call_tool("async_tool", {"x": 42})
⋮----
async def test_optional_context(self)
⋮----
"""Test that context is optional."""
⋮----
@mcp.tool
        def no_context(x: int) -> int
⋮----
result = await client.call_tool("no_context", {"x": 21})
⋮----
async def test_context_resource_access(self)
⋮----
"""Test that context can access resources."""
⋮----
@mcp.resource("test://data")
        def test_resource() -> str
⋮----
@mcp.tool
        async def tool_with_resource(ctx: Context) -> str
⋮----
r_iter = await ctx.read_resource("test://data")
r_list = list(r_iter)
⋮----
r = r_list[0]
⋮----
result = await client.call_tool("tool_with_resource", {})
⋮----
async def test_tool_decorator_with_tags(self)
⋮----
"""Test that the tool decorator properly sets tags."""
⋮----
@mcp.tool(tags={"example", "test-tag"})
        def sample_tool(x: int) -> int
# Verify the tool exists
⋮----
# Note: MCPTool from the client API doesn't expose tags
async def test_callable_object_with_context(self)
⋮----
"""Test that a callable object can be used as a tool with context."""
⋮----
class MyTool
⋮----
async def __call__(self, x: int, ctx: Context) -> int
⋮----
result = await client.call_tool("MyTool", {"x": 2})
⋮----
class TestToolEnabled
⋮----
async def test_toggle_enabled(self)
⋮----
@mcp.tool
        def sample_tool(x: int) -> int
⋮----
tool = await mcp.get_tool("sample_tool")
⋮----
async def test_tool_disabled_in_decorator(self)
⋮----
@mcp.tool(enabled=False)
        def sample_tool(x: int) -> int
⋮----
async def test_tool_toggle_enabled(self)
async def test_tool_toggle_disabled(self)
async def test_get_tool_and_disable(self)
⋮----
result = await client.list_tools()
⋮----
async def test_cant_call_disabled_tool(self)
class TestResource
⋮----
async def test_text_resource(self)
⋮----
def get_text()
resource = FunctionResource(
⋮----
result = await client.read_resource(AnyUrl("resource://test"))
assert result[0].text == "Hello, world!"  # type: ignore[attr-defined]
async def test_binary_resource(self)
⋮----
def get_binary()
⋮----
result = await client.read_resource(AnyUrl("resource://binary"))
assert result[0].blob == base64.b64encode(b"Binary data").decode()  # type: ignore[attr-defined]
async def test_file_resource_text(self, tmp_path: Path)
⋮----
# Create a text file
text_file = tmp_path / "test.txt"
⋮----
resource = FileResource(
⋮----
result = await client.read_resource(AnyUrl("file://test.txt"))
assert result[0].text == "Hello from file!"  # type: ignore[attr-defined]
async def test_file_resource_binary(self, tmp_path: Path)
⋮----
# Create a binary file
binary_file = tmp_path / "test.bin"
⋮----
result = await client.read_resource(AnyUrl("file://test.bin"))
assert result[0].blob == base64.b64encode(b"Binary file data").decode()  # type: ignore[attr-defined]
class TestResourceTags
⋮----
@mcp.resource("resource://1", tags={"a", "b"})
        def resource_1() -> str
⋮----
@mcp.resource("resource://2", tags={"b", "c"})
        def resource_2() -> str
⋮----
async def test_include_tags_all_resources(self)
⋮----
resources = await client.list_resources()
⋮----
async def test_include_tags_some_resources(self)
async def test_exclude_tags_all_resources(self)
async def test_exclude_tags_some_resources(self)
⋮----
async def test_read_included_resource(self)
⋮----
result = await client.read_resource(AnyUrl("resource://1"))
assert result[0].text == "1"  # type: ignore[attr-defined]
⋮----
async def test_read_excluded_resource(self)
class TestResourceContext
⋮----
async def test_resource_with_context_annotation_gets_context(self)
⋮----
@mcp.resource("resource://test")
        def resource_with_context(ctx: Context) -> str
⋮----
class TestResourceEnabled
⋮----
@mcp.resource("resource://data")
        def sample_resource() -> str
⋮----
resource = await mcp.get_resource("resource://data")
⋮----
async def test_resource_disabled_in_decorator(self)
⋮----
@mcp.resource("resource://data", enabled=False)
        def sample_resource() -> str
⋮----
async def test_resource_toggle_enabled(self)
async def test_resource_toggle_disabled(self)
async def test_get_resource_and_disable(self)
⋮----
result = await client.list_resources()
⋮----
async def test_cant_read_disabled_resource(self)
class TestResourceTemplates
⋮----
async def test_resource_with_params_not_in_uri(self)
⋮----
"""Test that a resource with function parameters raises an error if the URI
        parameters don't match"""
⋮----
@mcp.resource("resource://data")
            def get_data_fn(param: str) -> str
async def test_resource_with_uri_params_without_args(self)
⋮----
"""Test that a resource with URI parameters is automatically a template"""
⋮----
@mcp.resource("resource://{param}")
            def get_data() -> str
async def test_resource_with_untyped_params(self)
⋮----
"""Test that a resource with untyped parameters raises an error"""
⋮----
@mcp.resource("resource://{param}")
        def get_data(param) -> str
async def test_resource_matching_params(self)
⋮----
"""Test that a resource with matching URI and function parameters works"""
⋮----
@mcp.resource("resource://{name}/data")
        def get_data(name: str) -> str
⋮----
result = await client.read_resource(AnyUrl("resource://test/data"))
assert result[0].text == "Data for test"  # type: ignore[attr-defined]
async def test_resource_mismatched_params(self)
⋮----
"""Test that mismatched parameters raise an error"""
⋮----
@mcp.resource("resource://{name}/data")
            def get_data(user: str) -> str
async def test_resource_multiple_params(self)
⋮----
"""Test that multiple parameters work correctly"""
⋮----
@mcp.resource("resource://{org}/{repo}/data")
        def get_data(org: str, repo: str) -> str
⋮----
result = await client.read_resource(
assert result[0].text == "Data for cursor/fastmcp"  # type: ignore[attr-defined]
async def test_resource_multiple_mismatched_params(self)
⋮----
@mcp.resource("resource://{org}/{repo}/data")
            def get_data_mismatched(org: str, repo_2: str) -> str
"""Test that a resource with no parameters works as a regular resource"""
⋮----
@mcp.resource("resource://static")
        def get_static_data() -> str
⋮----
result = await client.read_resource(AnyUrl("resource://static"))
assert result[0].text == "Static data"  # type: ignore[attr-defined]
async def test_template_with_varkwargs(self)
⋮----
"""Test that a template can have **kwargs."""
⋮----
@mcp.resource("test://{x}/{y}/{z}")
        def func(**kwargs: int) -> int
⋮----
result = await client.read_resource(AnyUrl("test://1/2/3"))
assert result[0].text == "6"  # type: ignore[attr-defined]
async def test_template_with_default_params(self)
⋮----
"""Test that a template can have default parameters."""
⋮----
@mcp.resource("math://add/{x}")
        def add(x: int, y: int = 10) -> int
# Verify it's registered as a template
templates_dict = await mcp.get_resource_templates()
templates = list(templates_dict.values())
⋮----
# Call the template and verify it uses the default value
⋮----
result = await client.read_resource(AnyUrl("math://add/5"))
assert result[0].text == "15"  # type: ignore[attr-defined]
# Can also call with explicit params
result2 = await client.read_resource(AnyUrl("math://add/7"))
assert result2[0].text == "17"  # type: ignore[attr-defined]
async def test_template_to_resource_conversion(self)
⋮----
"""Test that a template can be converted to a resource."""
⋮----
# When accessed, should create a concrete resource
⋮----
async def test_template_decorator_with_tags(self)
⋮----
@mcp.resource("resource://{param}", tags={"template", "test-tag"})
        def template_resource(param: str) -> str
⋮----
template = templates_dict["resource://{param}"]
⋮----
async def test_template_decorator_wildcard_param(self)
⋮----
@mcp.resource("resource://{param*}")
        def template_resource(param: str) -> str
⋮----
assert result[0].text == "Template resource: test/data"  # type: ignore[attr-defined]
async def test_templates_match_in_order_of_definition(self)
⋮----
"""
        If a wildcard template is defined first, it will take priority over another
        matching template.
        """
⋮----
@mcp.resource("resource://{x}/{y}")
        def template_resource_with_params(x: str, y: str) -> str
⋮----
result = await client.read_resource(AnyUrl("resource://a/b/c"))
assert result[0].text == "Template resource 1: a/b/c"  # type: ignore[attr-defined]
result = await client.read_resource(AnyUrl("resource://a/b"))
assert result[0].text == "Template resource 1: a/b"  # type: ignore[attr-defined]
async def test_templates_shadow_each_other_reorder(self)
⋮----
"""
        If a wildcard template is defined second, it will *not* take priority over
        another matching template.
        """
⋮----
assert result[0].text == "Template resource 2: a/b/c"  # type: ignore[attr-defined]
⋮----
class TestResourceTemplatesTags
⋮----
@mcp.resource("resource://1/{param}", tags={"a", "b"})
        def template_resource_1(param: str) -> str
⋮----
@mcp.resource("resource://2/{param}", tags={"b", "c"})
        def template_resource_2(param: str) -> str
⋮----
resources = await client.list_resource_templates()
⋮----
async def test_exclude_takes_precedence_over_include(self)
async def test_read_resource_template_includes_tags(self)
⋮----
result = await client.read_resource("resource://1/x")
assert result[0].text == "Template resource 1: x"  # type: ignore[attr-defined]
⋮----
async def test_read_resource_template_excludes_tags(self)
⋮----
result = await client.read_resource("resource://2/x")
assert result[0].text == "Template resource 2: x"  # type: ignore[attr-defined]
class TestResourceTemplateContext
⋮----
async def test_resource_template_context(self)
⋮----
@mcp.resource("resource://{param}")
        def resource_template(param: str, ctx: Context) -> str
⋮----
assert result[0].text.startswith("Resource template: test 1")  # type: ignore[attr-defined]
async def test_resource_template_context_with_callable_object(self)
⋮----
class MyResource
⋮----
def __call__(self, param: str, ctx: Context) -> str
template = ResourceTemplate.from_function(
⋮----
class TestResourceTemplateEnabled
⋮----
@mcp.resource("resource://{param}")
        def sample_template(param: str) -> str
⋮----
template = await mcp.get_resource_template("resource://{param}")
⋮----
async def test_template_disabled_in_decorator(self)
⋮----
@mcp.resource("resource://{param}", enabled=False)
        def sample_template(param: str) -> str
⋮----
templates = await client.list_resource_templates()
⋮----
async def test_template_toggle_enabled(self)
async def test_template_toggle_disabled(self)
async def test_get_template_and_disable(self)
⋮----
result = await client.list_resource_templates()
⋮----
async def test_cant_read_disabled_template(self)
class TestPrompts
⋮----
"""Test prompt functionality in FastMCP server."""
async def test_prompt_decorator(self)
⋮----
"""Test that the prompt decorator registers prompts correctly."""
⋮----
@mcp.prompt
        def fn() -> str
prompts_dict = await mcp.get_prompts()
⋮----
prompt = prompts_dict["fn"]
⋮----
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
assert content[0].content.text == "Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_with_name(self)
⋮----
"""Test prompt decorator with custom name."""
⋮----
@mcp.prompt(name="custom_name")
        def fn() -> str
⋮----
prompt = prompts_dict["custom_name"]
⋮----
async def test_prompt_decorator_with_description(self)
⋮----
"""Test prompt decorator with custom description."""
⋮----
@mcp.prompt(description="A custom description")
        def fn() -> str
⋮----
async def test_prompt_decorator_with_parens(self)
async def test_list_prompts(self)
⋮----
"""Test listing prompts through MCP protocol."""
⋮----
@mcp.prompt
        def fn(name: str, optional: str = "default") -> str
⋮----
prompts = await client.list_prompts()
⋮----
async def test_list_prompts_with_enhanced_descriptions(self)
⋮----
"""Test that enhanced descriptions with JSON schema are visible via MCP protocol."""
⋮----
"""Analyze some data."""
⋮----
prompt = prompts[0]
⋮----
# Find each argument and verify schema enhancements
⋮----
args_by_name = {arg.name: arg for arg in prompt.arguments}
# String parameter should not have schema enhancement
name_arg = args_by_name["name"]
⋮----
# Non-string parameters should have schema enhancements
numbers_arg = args_by_name["numbers"]
⋮----
metadata_arg = args_by_name["metadata"]
⋮----
threshold_arg = args_by_name["threshold"]
⋮----
async def test_get_prompt(self)
⋮----
"""Test getting a prompt through MCP protocol."""
⋮----
@mcp.prompt
        def fn(name: str) -> str
⋮----
result = await client.get_prompt("fn", {"name": "World"})
⋮----
message = result.messages[0]
⋮----
content = message.content
assert content.text == "Hello, World!"  # type: ignore[attr-defined]
async def test_get_prompt_with_resource(self)
⋮----
"""Test getting a prompt that returns resource content."""
⋮----
@mcp.prompt
        def fn() -> PromptMessage
⋮----
result = await client.get_prompt("fn")
⋮----
content = result.messages[0].content
⋮----
async def test_get_unknown_prompt(self)
⋮----
"""Test error when getting unknown prompt."""
⋮----
async def test_get_prompt_missing_args(self)
⋮----
"""Test error when required arguments are missing."""
⋮----
@mcp.prompt
        def prompt_fn(name: str) -> str
⋮----
async def test_resource_decorator_with_tags(self)
⋮----
"""Test that the resource decorator supports tags."""
⋮----
@mcp.resource("resource://data", tags={"example", "test-tag"})
        def get_data() -> str
resources_dict = await mcp.get_resources()
resources = list(resources_dict.values())
⋮----
"""Test that the template decorator properly sets tags."""
⋮----
async def test_prompt_decorator_with_tags(self)
⋮----
"""Test that the prompt decorator properly sets tags."""
⋮----
@mcp.prompt(tags={"example", "test-tag"})
        def sample_prompt() -> str
⋮----
prompt = prompts_dict["sample_prompt"]
⋮----
class TestPromptEnabled
⋮----
@mcp.prompt
        def sample_prompt() -> str
⋮----
prompt = await mcp.get_prompt("sample_prompt")
⋮----
async def test_prompt_disabled_in_decorator(self)
⋮----
@mcp.prompt(enabled=False)
        def sample_prompt() -> str
⋮----
async def test_prompt_toggle_enabled(self)
async def test_prompt_toggle_disabled(self)
async def test_get_prompt_and_disable(self)
⋮----
result = await client.list_prompts()
⋮----
async def test_cant_get_disabled_prompt(self)
class TestPromptContext
⋮----
async def test_prompt_context(self)
⋮----
@mcp.prompt
        def prompt_fn(name: str, ctx: Context) -> str
⋮----
result = await client.get_prompt("prompt_fn", {"name": "World"})
⋮----
async def test_prompt_context_with_callable_object(self)
⋮----
class MyPrompt
⋮----
def __call__(self, name: str, ctx: Context) -> str
mcp.add_prompt(Prompt.from_function(MyPrompt(), name="my_prompt"))  # noqa: F821
⋮----
result = await client.get_prompt("my_prompt", {"name": "World"})
⋮----
assert message.content.text == "Hello, World! 1"  # type: ignore[attr-defined]
class TestPromptTags
⋮----
@mcp.prompt(tags={"a", "b"})
        def prompt_1() -> str
⋮----
@mcp.prompt(tags={"b", "c"})
        def prompt_2() -> str
⋮----
async def test_include_tags_all_prompts(self)
async def test_include_tags_some_prompts(self)
async def test_exclude_tags_all_prompts(self)
async def test_exclude_tags_some_prompts(self)
⋮----
async def test_read_prompt_includes_tags(self)
⋮----
result = await client.get_prompt("prompt_1")
assert result.messages[0].content.text == "1"  # type: ignore[attr-defined]
⋮----
async def test_read_prompt_excludes_tags(self)
⋮----
result = await client.get_prompt("prompt_2")
assert result.messages[0].content.text == "2"  # type: ignore[attr-defined]

================
File: tests/server/test_server.py
================
class TestCreateServer
⋮----
async def test_create_server(self)
⋮----
mcp = FastMCP(instructions="Server instructions")
⋮----
async def test_non_ascii_description(self)
⋮----
"""Test that FastMCP handles non-ASCII characters in descriptions correctly"""
mcp = FastMCP()
⋮----
def hello_world(name: str = "世界") -> str
⋮----
tools = await client.list_tools()
⋮----
tool = tools[0]
⋮----
result = await client.call_tool("hello_world", {})
⋮----
class TestTools
⋮----
async def test_mcp_tool_name(self)
⋮----
"""Test MCPTool name for add_tool (key != tool.name)."""
⋮----
@mcp.tool
        def fn(x: int) -> int
mcp_tools = await mcp._mcp_list_tools()
⋮----
async def test_mcp_tool_custom_name(self)
⋮----
@mcp.tool(name="custom_name")
        def fn(x: int) -> int
⋮----
async def test_remove_tool_successfully(self)
⋮----
"""Test that FastMCP.remove_tool removes the tool from the registry."""
⋮----
@mcp.tool(name="adder")
        def add(a: int, b: int) -> int
mcp_tools = await mcp.get_tools()
⋮----
async def test_add_tool_at_init(self)
⋮----
def f(x: int) -> int
def g(x: int) -> int
⋮----
"""add two to a number"""
⋮----
g_tool = FunctionTool.from_function(g, name="g-tool")
mcp = FastMCP(tools=[f, g_tool])
tools = await mcp.get_tools()
⋮----
class TestToolDecorator
⋮----
async def test_no_tools_before_decorator(self)
async def test_tool_decorator(self)
⋮----
@mcp.tool
        def add(x: int, y: int) -> int
⋮----
result = await client.call_tool("add", {"x": 1, "y": 2})
⋮----
async def test_tool_decorator_without_parentheses(self)
⋮----
"""Test that @tool decorator works without parentheses."""
⋮----
# Test the @tool syntax without parentheses
⋮----
# Verify the tool was registered correctly
⋮----
# Verify it can be called
⋮----
async def test_tool_decorator_with_name(self)
⋮----
@mcp.tool(name="custom-add")
        def add(x: int, y: int) -> int
⋮----
result = await client.call_tool("custom-add", {"x": 1, "y": 2})
⋮----
async def test_tool_decorator_with_description(self)
⋮----
@mcp.tool(description="Add two numbers")
        def add(x: int, y: int) -> int
tools = await mcp._mcp_list_tools()
⋮----
async def test_tool_decorator_instance_method(self)
⋮----
class MyClass
⋮----
def __init__(self, x: int)
def add(self, y: int) -> int
obj = MyClass(10)
⋮----
result = await client.call_tool("add", {"y": 2})
⋮----
async def test_tool_decorator_classmethod(self)
⋮----
x: int = 10
⋮----
@classmethod
            def add(cls, y: int) -> int
⋮----
async def test_tool_decorator_staticmethod(self)
⋮----
@mcp.tool
@staticmethod
            def add(x: int, y: int) -> int
⋮----
async def test_tool_decorator_async_function(self)
⋮----
@mcp.tool
        async def add(x: int, y: int) -> int
⋮----
async def test_tool_decorator_classmethod_error(self)
⋮----
class MyClass
⋮----
@mcp.tool
@classmethod
                def add(cls, y: int) -> None
async def test_tool_decorator_classmethod_async_function(self)
⋮----
x = 10
⋮----
@classmethod
            async def add(cls, y: int) -> int
⋮----
async def test_tool_decorator_staticmethod_async_function(self)
⋮----
@staticmethod
            async def add(x: int, y: int) -> int
⋮----
async def test_tool_decorator_staticmethod_order(self)
⋮----
"""Test that the recommended decorator order works for static methods"""
⋮----
@mcp.tool
@staticmethod
            def add_v1(x: int, y: int) -> int
# Test that the recommended order works
⋮----
result = await client.call_tool("add_v1", {"x": 1, "y": 2})
⋮----
async def test_tool_decorator_with_tags(self)
⋮----
"""Test that the tool decorator properly sets tags."""
⋮----
@mcp.tool(tags={"example", "test-tag"})
        def sample_tool(x: int) -> int
# Verify the tags were set correctly
tools = await mcp._tool_manager.list_tools()
⋮----
async def test_add_tool_with_custom_name(self)
⋮----
"""Test adding a tool with a custom name using server.add_tool()."""
⋮----
def multiply(a: int, b: int) -> int
⋮----
"""Multiply two numbers."""
⋮----
# Check that the tool is registered with the custom name
⋮----
# Call the tool by its custom name
⋮----
result = await client.call_tool("custom_multiply", {"a": 5, "b": 3})
⋮----
# Original name should not be registered
⋮----
async def test_tool_with_annotated_arguments(self)
⋮----
"""Test that tools with annotated arguments work correctly."""
⋮----
tool = (await mcp.get_tools())["add"]
⋮----
async def test_tool_with_field_defaults(self)
async def test_tool_direct_function_call(self)
⋮----
"""Test that tools can be registered via direct function call."""
⋮----
def standalone_function(x: int, y: int) -> int
⋮----
"""A standalone function to be registered."""
⋮----
# Register it directly using the new syntax
result_fn = mcp.tool(standalone_function, name="direct_call_tool")
# The function should be returned unchanged
⋮----
result = await client.call_tool("direct_call_tool", {"x": 5, "y": 3})
⋮----
async def test_tool_decorator_with_string_name(self)
⋮----
"""Test that @tool("custom_name") syntax works correctly."""
⋮----
@mcp.tool("string_named_tool")
        def my_function(x: int) -> str
⋮----
"""A function with a string name."""
⋮----
# Verify the tool was registered with the custom name
⋮----
assert "my_function" not in tools  # Original name should not be registered
⋮----
result = await client.call_tool("string_named_tool", {"x": 42})
⋮----
async def test_tool_decorator_conflicting_names_error(self)
⋮----
"""Test that providing both positional and keyword name raises an error."""
⋮----
@mcp.tool("positional_name", name="keyword_name")
            def my_function(x: int) -> str
async def test_tool_decorator_with_output_schema(self)
⋮----
@mcp.tool(output_schema={"type": "integer"})
            def my_function(x: int) -> str
class TestResourceDecorator
⋮----
async def test_no_resources_before_decorator(self)
async def test_resource_decorator(self)
⋮----
@mcp.resource("resource://data")
        def get_data() -> str
⋮----
result = await client.read_resource("resource://data")
assert result[0].text == "Hello, world!"  # type: ignore[attr-defined]
async def test_resource_decorator_incorrect_usage(self)
⋮----
@mcp.resource  # Missing parentheses #type: ignore
@mcp.resource  # Missing parentheses #type: ignore
            def get_data() -> str
async def test_resource_decorator_with_name(self)
⋮----
@mcp.resource("resource://data", name="custom-data")
        def get_data() -> str
resources_dict = await mcp.get_resources()
resources = list(resources_dict.values())
⋮----
async def test_resource_decorator_with_description(self)
⋮----
@mcp.resource("resource://data", description="Data resource")
        def get_data() -> str
⋮----
async def test_resource_decorator_with_tags(self)
⋮----
"""Test that the resource decorator properly sets tags."""
⋮----
@mcp.resource("resource://data", tags={"example", "test-tag"})
        def get_data() -> str
⋮----
async def test_resource_decorator_instance_method(self)
⋮----
def __init__(self, prefix: str)
def get_data(self) -> str
obj = MyClass("My prefix:")
⋮----
assert result[0].text == "My prefix: Hello, world!"  # type: ignore[attr-defined]
async def test_resource_decorator_classmethod(self)
⋮----
prefix = "Class prefix:"
⋮----
@classmethod
            def get_data(cls) -> str
⋮----
assert result[0].text == "Class prefix: Hello, world!"  # type: ignore[attr-defined]
async def test_resource_decorator_classmethod_error(self)
⋮----
@mcp.resource("resource://data")
@classmethod
                def get_data(cls) -> None
async def test_resource_decorator_staticmethod(self)
⋮----
@mcp.resource("resource://data")
@staticmethod
            def get_data() -> str
⋮----
assert result[0].text == "Static Hello, world!"  # type: ignore[attr-defined]
async def test_resource_decorator_async_function(self)
⋮----
@mcp.resource("resource://data")
        async def get_data() -> str
⋮----
assert result[0].text == "Async Hello, world!"  # type: ignore[attr-defined]
async def test_resource_decorator_staticmethod_order(self)
⋮----
"""Test that both decorator orders work for static methods"""
⋮----
@mcp.resource("resource://data")  # type: ignore[misc]  # Type checker warns but runtime works
⋮----
@mcp.resource("resource://data")  # type: ignore[misc]  # Type checker warns but runtime works
@staticmethod
            def get_data() -> str
⋮----
class TestTemplateDecorator
⋮----
async def test_template_decorator(self)
⋮----
@mcp.resource("resource://{name}/data")
        def get_data(name: str) -> str
templates_dict = await mcp.get_resource_templates()
templates = list(templates_dict.values())
⋮----
result = await client.read_resource("resource://test/data")
assert result[0].text == "Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_incorrect_usage(self)
⋮----
@mcp.resource  # Missing parentheses #type: ignore
            def get_data(name: str) -> str
async def test_template_decorator_with_name(self)
⋮----
@mcp.resource("resource://{name}/data", name="custom-template")
        def get_data(name: str) -> str
⋮----
assert result[0].text == "Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_with_description(self)
⋮----
@mcp.resource("resource://{name}/data", description="Template description")
        def get_data(name: str) -> str
⋮----
async def test_template_decorator_instance_method(self)
⋮----
def get_data(self, name: str) -> str
⋮----
template = ResourceTemplate.from_function(
⋮----
assert result[0].text == "My prefix: Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_classmethod(self)
⋮----
@classmethod
            def get_data(cls, name: str) -> str
⋮----
assert result[0].text == "Class prefix: Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_staticmethod(self)
⋮----
@mcp.resource("resource://{name}/data")
@staticmethod
            def get_data(name: str) -> str
⋮----
assert result[0].text == "Static Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_async_function(self)
⋮----
@mcp.resource("resource://{name}/data")
        async def get_data(name: str) -> str
⋮----
assert result[0].text == "Async Data for test"  # type: ignore[attr-defined]
async def test_template_decorator_with_tags(self)
⋮----
"""Test that the template decorator properly sets tags."""
⋮----
@mcp.resource("resource://{param}", tags={"template", "test-tag"})
        def template_resource(param: str) -> str
⋮----
template = templates_dict["resource://{param}"]
⋮----
async def test_template_decorator_wildcard_param(self)
⋮----
@mcp.resource("resource://{param*}")
        def template_resource(param: str) -> str
⋮----
template = templates_dict["resource://{param*}"]
⋮----
class TestPromptDecorator
⋮----
async def test_prompt_decorator(self)
⋮----
@mcp.prompt
        def fn() -> str
prompts_dict = await mcp.get_prompts()
⋮----
prompt = prompts_dict["fn"]
⋮----
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
assert content[0].content.text == "Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_without_parentheses(self)
⋮----
# This should now work correctly (not raise an error)
@mcp.prompt  # No parentheses - this is now supported
@mcp.prompt  # No parentheses - this is now supported
        def fn() -> str
# Verify the prompt was registered correctly
prompts = await mcp.get_prompts()
⋮----
result = await client.get_prompt("fn")
⋮----
assert result.messages[0].content.text == "Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_with_name(self)
⋮----
@mcp.prompt(name="custom_name")
        def fn() -> str
⋮----
prompt = prompts_dict["custom_name"]
⋮----
async def test_prompt_decorator_with_description(self)
⋮----
@mcp.prompt(description="A custom description")
        def fn() -> str
⋮----
async def test_prompt_decorator_with_parameters(self)
⋮----
@mcp.prompt
        def test_prompt(name: str, greeting: str = "Hello") -> str
⋮----
prompt = prompts_dict["test_prompt"]
⋮----
result = await client.get_prompt("test_prompt", {"name": "World"})
⋮----
message = result.messages[0]
assert message.content.text == "Hello, World!"  # type: ignore[attr-defined]
result = await client.get_prompt(
⋮----
assert message.content.text == "Hi, World!"  # type: ignore[attr-defined]
async def test_prompt_decorator_instance_method(self)
⋮----
def test_prompt(self) -> str
⋮----
result = await client.get_prompt("test_prompt")
⋮----
assert message.content.text == "My prefix: Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_classmethod(self)
⋮----
@classmethod
            def test_prompt(cls) -> str
⋮----
assert message.content.text == "Class prefix: Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_classmethod_error(self)
⋮----
@mcp.prompt
@classmethod
                def test_prompt(cls) -> None
async def test_prompt_decorator_staticmethod(self)
⋮----
@mcp.prompt
@staticmethod
            def test_prompt() -> str
⋮----
assert message.content.text == "Static Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_async_function(self)
⋮----
@mcp.prompt
        async def test_prompt() -> str
⋮----
assert message.content.text == "Async Hello, world!"  # type: ignore[attr-defined]
async def test_prompt_decorator_with_tags(self)
⋮----
"""Test that the prompt decorator properly sets tags."""
⋮----
@mcp.prompt(tags={"example", "test-tag"})
        def sample_prompt() -> str
⋮----
prompt = prompts_dict["sample_prompt"]
⋮----
async def test_prompt_decorator_with_string_name(self)
⋮----
"""Test that @prompt(\"custom_name\") syntax works correctly."""
⋮----
@mcp.prompt("string_named_prompt")
        def my_function() -> str
# Verify the prompt was registered with the custom name
⋮----
assert "my_function" not in prompts  # Original name should not be registered
⋮----
result = await client.get_prompt("string_named_prompt")
⋮----
assert result.messages[0].content.text == "Hello from string named prompt!"  # type: ignore[attr-defined]
async def test_prompt_direct_function_call(self)
⋮----
"""Test that prompts can be registered via direct function call."""
⋮----
def standalone_function() -> str
⋮----
result_fn = mcp.prompt(standalone_function, name="direct_call_prompt")
⋮----
result = await client.get_prompt("direct_call_prompt")
⋮----
assert result.messages[0].content.text == "Hello from direct call!"  # type: ignore[attr-defined]
async def test_prompt_decorator_conflicting_names_error(self)
⋮----
"""Test that providing both positional and keyword names raises an error."""
⋮----
@mcp.prompt("positional_name", name="keyword_name")
            def my_function() -> str
async def test_prompt_decorator_staticmethod_order(self)
⋮----
@mcp.prompt  # type: ignore[misc]  # Type checker warns but runtime works
⋮----
@mcp.prompt  # type: ignore[misc]  # Type checker warns but runtime works
@staticmethod
            def test_prompt() -> str
⋮----
class TestResourcePrefixHelpers
⋮----
# Normal paths
⋮----
# Absolute paths (with triple slash)
⋮----
# Empty prefix should return the original URI
⋮----
# Different protocols
⋮----
# Prefixes with special characters
⋮----
# Empty paths
⋮----
def test_add_resource_prefix(self, uri, prefix, expected)
⋮----
"""Test that add_resource_prefix correctly adds prefixes to URIs."""
result = add_resource_prefix(uri, prefix)
⋮----
def test_add_resource_prefix_invalid_uri(self, invalid_uri)
⋮----
"""Test that add_resource_prefix raises ValueError for invalid URIs."""
⋮----
# URI without the expected prefix should return the original URI
⋮----
# Prefixes with special characters (that need escaping in regex)
⋮----
def test_remove_resource_prefix(self, uri, prefix, expected)
⋮----
"""Test that remove_resource_prefix correctly removes prefixes from URIs."""
result = remove_resource_prefix(uri, prefix)
⋮----
def test_remove_resource_prefix_invalid_uri(self, invalid_uri)
⋮----
"""Test that remove_resource_prefix raises ValueError for invalid URIs."""
⋮----
# URI with prefix
⋮----
# URI with another prefix
⋮----
# URI with prefix as a substring but not at path start
⋮----
# Empty prefix
⋮----
# Prefix with special characters
⋮----
def test_has_resource_prefix(self, uri, prefix, expected)
⋮----
"""Test that has_resource_prefix correctly identifies prefixes in URIs."""
result = has_resource_prefix(uri, prefix)
⋮----
def test_has_resource_prefix_invalid_uri(self, invalid_uri)
⋮----
"""Test that has_resource_prefix raises ValueError for invalid URIs."""
⋮----
class TestResourcePrefixMounting
⋮----
"""Test resource prefixing in mounted servers."""
async def test_mounted_server_resource_prefixing(self)
⋮----
"""Test that resources in mounted servers use the correct prefix format."""
# Create a server with resources
server = FastMCP(name="ResourceServer")
⋮----
@server.resource("resource://test-resource")
        def get_resource()
⋮----
@server.resource("resource:///absolute/path")
        def get_absolute_resource()
⋮----
@server.resource("resource://{param}/template")
        def get_template_resource(param: str)
# Create a main server and mount the resource server
main_server = FastMCP(name="MainServer")
⋮----
# Check that the resources are mounted with the correct prefixes
resources = await main_server.get_resources()
templates = await main_server.get_resource_templates()
⋮----
# Test that prefixed resources can be accessed
⋮----
# Regular resource
result = await client.read_resource("resource://prefix/test-resource")
assert result[0].text == "Resource content"  # type: ignore[attr-defined]
# Absolute path resource
result = await client.read_resource("resource://prefix//absolute/path")
assert result[0].text == "Absolute resource content"  # type: ignore[attr-defined]
# Template resource
result = await client.read_resource(
assert result[0].text == "Template resource with param-value"  # type: ignore[attr-defined]
⋮----
# Absolute path
⋮----
# Non-matching prefix
⋮----
# Different protocol
⋮----
"""Test that resource prefix utility functions correctly match and strip resource prefixes."""
⋮----
# Create a basic server to get the default resource prefix format
server = FastMCP()
# Test matching
⋮----
# Test stripping
⋮----
async def test_import_server_with_new_prefix_format(self)
⋮----
"""Test that import_server correctly uses the new prefix format."""
⋮----
source_server = FastMCP(name="SourceServer")
⋮----
@source_server.resource("resource://test-resource")
        def get_resource()
⋮----
@source_server.resource("resource:///absolute/path")
        def get_absolute_resource()
⋮----
@source_server.resource("resource://{param}/template")
        def get_template_resource(param: str)
# Create target server and import the source server
target_server = FastMCP(name="TargetServer")
⋮----
# Check that the resources were imported with the correct prefixes
resources = await target_server.get_resources()
templates = await target_server.get_resource_templates()
⋮----
# Verify we can access the resources
⋮----
result = await client.read_resource("resource://imported/test-resource")
⋮----
result = await client.read_resource("resource://imported//absolute/path")
⋮----
class TestShouldIncludeComponent
⋮----
def test_no_filters_returns_true(self)
⋮----
"""Test that when no include or exclude filters are provided, always returns True."""
tool = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={})
mcp = FastMCP(tools=[tool])
result = mcp._should_enable_component(tool)
⋮----
def test_exclude_string_tag_present_returns_false(self)
⋮----
"""Test that when an exclude string tag is present in tags, returns False."""
tool = Tool(
mcp = FastMCP(tools=[tool], exclude_tags={"exclude_me"})
⋮----
def test_exclude_string_tag_absent_returns_true(self)
⋮----
"""Test that when an exclude string tag is not present in tags, returns True."""
⋮----
def test_multiple_exclude_tags_any_match_returns_false(self)
⋮----
"""Test that when any exclude tag matches, returns False."""
tool = Tool(name="test_tool", tags={"tag1", "tag2", "tag3"}, parameters={})
mcp = FastMCP(
⋮----
def test_include_string_tag_present_returns_true(self)
⋮----
"""Test that when an include string tag is present in tags, returns True."""
⋮----
mcp = FastMCP(tools=[tool], include_tags={"include_me"})
⋮----
def test_include_string_tag_absent_returns_false(self)
⋮----
"""Test that when an include string tag is not present in tags, returns False."""
⋮----
def test_multiple_include_tags_any_match_returns_true(self)
⋮----
"""Test that when any include tag matches, returns True."""
⋮----
def test_multiple_include_tags_none_match_returns_false(self)
⋮----
"""Test that when no include tags match, returns False."""
⋮----
mcp = FastMCP(tools=[tool], include_tags={"not_present", "also_not_present"})
⋮----
def test_exclude_takes_precedence_over_include(self)
⋮----
"""Test that exclude tags take precedence over include tags."""
⋮----
mcp = FastMCP(tools=[tool], include_tags={"tag1"}, exclude_tags={"exclude_me"})
⋮----
def test_empty_include_exclude_sets(self)
⋮----
"""Test behavior with empty include/exclude sets."""
# Empty include set means nothing matches
tool1 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={})
mcp1 = FastMCP(tools=[tool1], include_tags=set())
result = mcp1._should_enable_component(tool1)
⋮----
# Empty exclude set means nothing excluded
tool2 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={})
mcp2 = FastMCP(tools=[tool2], exclude_tags=set())
result = mcp2._should_enable_component(tool2)
⋮----
def test_empty_tags_with_filters(self)
⋮----
"""Test behavior when input tags are empty."""
# With include filters, empty tags should not match
tool1 = Tool(name="test_tool", tags=set(), parameters={})
mcp1 = FastMCP(tools=[tool1], include_tags={"required_tag"})
⋮----
# With exclude filters but no include, empty tags should pass
tool2 = Tool(name="test_tool", tags=set(), parameters={})
mcp2 = FastMCP(tools=[tool2], exclude_tags={"bad_tag"})

================
File: tests/server/test_tool_annotations.py
================
async def test_tool_annotations_in_tool_manager()
⋮----
"""Test that tool annotations are correctly stored in the tool manager."""
mcp = FastMCP("Test Server")
⋮----
def echo(message: str) -> str
⋮----
"""Echo back the message provided."""
⋮----
# Check internal tool objects directly
tools_dict = await mcp._tool_manager.get_tools()
tools = list(tools_dict.values())
⋮----
async def test_tool_annotations_in_mcp_protocol()
⋮----
"""Test that tool annotations are correctly propagated to MCP tools list."""
⋮----
# Check via MCP protocol
mcp_tools = await mcp._mcp_list_tools()
⋮----
async def test_tool_annotations_in_client_api()
⋮----
"""Test that tool annotations are correctly accessible via client API."""
⋮----
# Check via client API
⋮----
tools_result = await client.list_tools()
⋮----
async def test_provide_tool_annotations_as_dict_to_decorator()
async def test_direct_tool_annotations_in_tool_manager()
⋮----
"""Test direct ToolAnnotations object is correctly stored in tool manager."""
⋮----
annotations = ToolAnnotations(
⋮----
@mcp.tool(annotations=annotations)
    def modify(data: dict[str, Any]) -> dict[str, Any]
⋮----
"""Modify the data provided."""
⋮----
async def test_direct_tool_annotations_in_client_api()
⋮----
"""Test direct ToolAnnotations object is correctly accessible via client API."""
⋮----
async def test_add_tool_method_annotations()
⋮----
"""Test that tool annotations work with add_tool method."""
⋮----
def create_item(name: str, value: int) -> dict[str, Any]
⋮----
"""Create a new item."""
⋮----
tool = Tool.from_function(
⋮----
async def test_tool_functionality_with_annotations()
⋮----
"""Test that tool functionality is preserved when using annotations."""
⋮----
# Use the tool to verify functionality is preserved
⋮----
result = await client.call_tool(

================
File: tests/server/test_tool_exclude_args.py
================
async def test_tool_exclude_args_in_tool_manager()
⋮----
"""Test that tool args are excluded in the tool manager."""
mcp = FastMCP("Test Server")
⋮----
@mcp.tool(exclude_args=["state"])
    def echo(message: str, state: dict[str, Any] | None = None) -> str
⋮----
"""Echo back the message provided."""
⋮----
# State was read
⋮----
tools_dict = await mcp._tool_manager.get_tools()
tools = list(tools_dict.values())
⋮----
async def test_tool_exclude_args_without_default_value_raises_error()
⋮----
"""Test that excluding args without default values raises ValueError"""
⋮----
@mcp.tool(exclude_args=["state"])
        def echo(message: str, state: dict[str, Any] | None) -> str
⋮----
"""Echo back the message provided."""
⋮----
# State was read
⋮----
async def test_add_tool_method_exclude_args()
⋮----
"""Test that tool exclude_args work with the add_tool method."""
⋮----
"""Create a new item."""
⋮----
tool = Tool.from_function(
⋮----
# Check internal tool objects directly
⋮----
async def test_tool_functionality_with_exclude_args()
⋮----
"""Test that tool functionality is preserved when using exclude_args."""
⋮----
# state was read
⋮----
# Use the tool to verify functionality is preserved
⋮----
result = await client.call_tool(

================
File: tests/test_servers/fastmcp_server.py
================
USERS = [
server = FastMCP("TestServer")
# --- Tools ---
⋮----
@server.tool
def greet(name: str) -> str
⋮----
"""Greet someone by name."""
⋮----
@server.tool
def add(a: int, b: int) -> int
⋮----
"""Add two numbers together."""
⋮----
@server.tool
def error_tool()
⋮----
"""This tool always raises an error."""
⋮----
# --- Resources ---
⋮----
@server.resource(uri="resource://wave")
def wave() -> str
⋮----
@server.resource(uri="data://users")
async def get_users() -> list[dict[str, Any]]
⋮----
@server.resource(uri="data://user/{user_id}")
async def get_user(user_id: str) -> dict[str, Any] | None
# --- Prompts ---
⋮----
@server.prompt
def welcome(name: str) -> str

================
File: tests/test_servers/sse.py
================


================
File: tests/test_servers/stdio.py
================


================
File: tests/tools/test_tool_manager.py
================
class TestAddTools
⋮----
async def test_basic_function(self)
⋮----
"""Test registering and running a basic function."""
def add(a: int, b: int) -> int
⋮----
"""Add two numbers."""
⋮----
manager = ToolManager()
tool = Tool.from_function(add)
⋮----
tool = await manager.get_tool("add")
⋮----
async def test_async_function(self)
⋮----
"""Test registering and running an async function."""
async def fetch_data(url: str) -> str
⋮----
"""Fetch data from URL."""
⋮----
tool = Tool.from_function(fetch_data)
⋮----
tool = await manager.get_tool("fetch_data")
⋮----
async def test_pydantic_model_function(self)
⋮----
"""Test registering a function that takes a Pydantic model."""
class UserInput(BaseModel)
⋮----
name: str
age: int
def create_user(user: UserInput, flag: bool) -> dict
⋮----
"""Create a new user."""
⋮----
tool = Tool.from_function(create_user)
⋮----
tool = await manager.get_tool("create_user")
⋮----
async def test_callable_object(self)
⋮----
class Adder
⋮----
"""Adds two numbers."""
def __call__(self, x: int, y: int) -> int
⋮----
"""ignore this"""
⋮----
tool = Tool.from_function(Adder())
⋮----
tool = await manager.get_tool("Adder")
⋮----
async def test_async_callable_object(self)
⋮----
async def __call__(self, x: int, y: int) -> int
⋮----
async def test_tool_with_image_return(self)
⋮----
def image_tool(data: bytes) -> Image
⋮----
tool = Tool.from_function(image_tool)
⋮----
tool = await manager.get_tool("image_tool")
result = await tool.run({"data": "test.png"})
⋮----
def test_add_noncallable_tool(self)
⋮----
tool = Tool.from_function(1)  # type: ignore
⋮----
def test_add_lambda(self)
⋮----
tool = Tool.from_function(lambda x: x, name="my_tool")
⋮----
def test_add_lambda_with_no_name(self)
⋮----
tool = Tool.from_function(lambda x: x)
⋮----
async def test_remove_tool_successfully(self)
⋮----
"""Test removing an added tool by key."""
⋮----
def test_remove_tool_missing_key(self)
⋮----
"""Test removing a tool that does not exist raises NotFoundError."""
⋮----
async def test_warn_on_duplicate_tools(self, caplog)
⋮----
"""Test warning on duplicate tools."""
manager = ToolManager(duplicate_behavior="warn")
def test_fn(x: int) -> int
tool1 = Tool.from_function(test_fn, name="test_tool")
⋮----
tool2 = Tool.from_function(test_fn, name="test_tool")
⋮----
# Should have the tool
⋮----
def test_disable_warn_on_duplicate_tools(self, caplog)
⋮----
"""Test disabling warning on duplicate tools."""
def f(x: int) -> int
manager = ToolManager(duplicate_behavior="ignore")
tool1 = Tool.from_function(f)
⋮----
tool2 = Tool.from_function(f)
⋮----
def test_error_on_duplicate_tools(self)
⋮----
"""Test error on duplicate tools."""
manager = ToolManager(duplicate_behavior="error")
⋮----
async def test_replace_duplicate_tools(self)
⋮----
"""Test replacing duplicate tools."""
manager = ToolManager(duplicate_behavior="replace")
def original_fn(x: int) -> int
def replacement_fn(x: int) -> int
tool1 = Tool.from_function(original_fn, name="test_tool")
⋮----
result = Tool.from_function(replacement_fn, name="test_tool")
⋮----
# Should have replaced with the new tool
tool = await manager.get_tool("test_tool")
⋮----
async def test_ignore_duplicate_tools(self)
⋮----
"""Test ignoring duplicate tools."""
⋮----
# Should keep the original
⋮----
# Result should be the original tool
⋮----
class TestToolTags
⋮----
"""Test functionality related to tool tags."""
async def test_add_tool_with_tags(self)
⋮----
"""Test adding tags to a tool."""
def example_tool(x: int) -> int
⋮----
"""An example tool with tags."""
⋮----
tool = Tool.from_function(example_tool, tags={"math", "utility"})
⋮----
tool = await manager.get_tool("example_tool")
⋮----
async def test_add_tool_with_empty_tags(self)
⋮----
"""Test adding a tool with empty tags set."""
⋮----
"""An example tool with empty tags."""
⋮----
tool = Tool.from_function(example_tool, tags=set())
⋮----
async def test_add_tool_with_none_tags(self)
⋮----
"""Test adding a tool with None tags."""
⋮----
"""An example tool with None tags."""
⋮----
tool = Tool.from_function(example_tool, tags=None)
⋮----
async def test_list_tools_with_tags(self)
⋮----
"""Test listing tools with specific tags."""
def math_tool(x: int) -> int
⋮----
"""A math tool."""
⋮----
def string_tool(x: str) -> str
⋮----
"""A string tool."""
⋮----
def mixed_tool(x: int) -> str
⋮----
"""A tool with multiple tags."""
⋮----
tool1 = Tool.from_function(math_tool, tags={"math"})
⋮----
tool2 = Tool.from_function(string_tool, tags={"string", "utility"})
⋮----
tool3 = Tool.from_function(mixed_tool, tags={"math", "utility", "string"})
⋮----
# Check if we can filter by tags when listing tools
math_tools = [
⋮----
utility_tools = [
⋮----
class TestCallTools
⋮----
async def test_call_tool(self)
⋮----
result = await manager.call_tool("add", {"a": 1, "b": 2})
assert result.content[0].text == "3"  # type: ignore[attr-defined]
⋮----
async def test_call_async_tool(self)
⋮----
async def double(n: int) -> int
⋮----
"""Double a number."""
⋮----
tool = Tool.from_function(double)
⋮----
result = await manager.call_tool("double", {"n": 5})
assert result.content[0].text == "10"  # type: ignore[attr-defined]
⋮----
async def test_call_tool_callable_object(self)
⋮----
result = await manager.call_tool("Adder", {"x": 1, "y": 2})
⋮----
async def test_call_tool_callable_object_async(self)
async def test_call_tool_with_default_args(self)
⋮----
def add(a: int, b: int = 1) -> int
⋮----
result = await manager.call_tool("add", {"a": 1})
assert result.content[0].text == "2"  # type: ignore[attr-defined]
⋮----
async def test_call_tool_with_missing_args(self)
async def test_call_unknown_tool(self)
async def test_call_tool_with_list_int_input(self)
⋮----
def sum_vals(vals: list[int]) -> int
⋮----
tool = Tool.from_function(sum_vals)
⋮----
result = await manager.call_tool("sum_vals", {"vals": [1, 2, 3]})
assert result.content[0].text == "6"  # type: ignore[attr-defined]
⋮----
async def test_call_tool_with_list_str_or_str_input(self)
⋮----
def concat_strs(vals: list[str] | str) -> str
⋮----
tool = Tool.from_function(concat_strs)
⋮----
# Try both with plain python object and with JSON list
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]})
assert result.content[0].text == "abc"  # type: ignore[attr-defined]
⋮----
result = await manager.call_tool("concat_strs", {"vals": "a"})
assert result.content[0].text == "a"  # type: ignore[attr-defined]
⋮----
async def test_call_tool_with_complex_model(self)
⋮----
class MyShrimpTank(BaseModel)
⋮----
class Shrimp(BaseModel)
shrimp: list[Shrimp]
x: None
def name_shrimp(tank: MyShrimpTank, ctx: Context | None) -> list[str]
⋮----
tool = Tool.from_function(name_shrimp)
⋮----
mcp = FastMCP()
context = Context(fastmcp=mcp)
⋮----
result = await manager.call_tool(
assert result.content[0].text == '[\n  "rex",\n  "gertrude"\n]'  # type: ignore[attr-defined]
⋮----
async def test_call_tool_with_custom_serializer(self)
⋮----
"""Test that a custom serializer provided to FastMCP is used by tools."""
def custom_serializer(data: Any) -> str
# Instantiate FastMCP with the custom serializer
mcp = FastMCP(tool_serializer=custom_serializer)
manager = mcp._tool_manager
⋮----
@mcp.tool
        def get_data() -> dict
result = await manager.call_tool("get_data", {})
assert result.content[0].text == 'CUSTOM:{"key": "value", "number": 123}'  # type: ignore[attr-defined]
⋮----
async def test_call_tool_with_list_result_custom_serializer(self)
⋮----
"""Test that a custom serializer provided to FastMCP is used by tools that return lists."""
⋮----
@mcp.tool
        def get_data() -> list[dict]
⋮----
result.content[0].text  # type: ignore[attr-defined]
== 'CUSTOM:[{"key": "value", "number": 123}, {"key": "value2", "number": 456}]'  # type: ignore[attr-defined]
⋮----
async def test_custom_serializer_fallback_on_error(self)
⋮----
"""Test that a broken custom serializer gracefully falls back."""
uuid_result = uuid.uuid4()
⋮----
@mcp.tool
        def get_data() -> uuid.UUID
⋮----
class TestToolSchema
⋮----
async def test_context_arg_excluded_from_schema(self)
⋮----
def something(a: int, ctx: Context) -> int
⋮----
tool = Tool.from_function(something)
⋮----
async def test_optional_context_arg_excluded_from_schema(self)
⋮----
def something(a: int, ctx: Context | None) -> int
⋮----
async def test_annotated_context_arg_excluded_from_schema(self)
⋮----
def something(a: int, ctx: Annotated[Context | int | None, "ctx"]) -> int
⋮----
class TestContextHandling
⋮----
"""Test context handling in the tool manager."""
def test_context_parameter_detection(self)
⋮----
"""Test that context parameters are properly detected in
        Tool.from_function()."""
def tool_with_context(x: int, ctx: Context) -> str
⋮----
tool = Tool.from_function(tool_with_context)
⋮----
def tool_without_context(x: int) -> str
⋮----
async def test_context_injection(self)
⋮----
"""Test that context is properly injected during tool execution."""
⋮----
result = await manager.call_tool("tool_with_context", {"x": 42})
assert result.content[0].text == "42"  # type: ignore[attr-defined]
⋮----
async def test_context_injection_async(self)
⋮----
"""Test that context is properly injected in async tools."""
async def async_tool(x: int, ctx: Context) -> str
⋮----
tool = Tool.from_function(async_tool)
⋮----
result = await manager.call_tool("async_tool", {"x": 42})
⋮----
async def test_context_optional(self)
⋮----
"""Test that context is optional when calling tools."""
def tool_with_context(x: int, ctx: Context | None) -> int
⋮----
# Should not raise an error when context is not provided
⋮----
def test_parameterized_context_parameter_detection(self)
def test_annotated_context_parameter_detection(self)
⋮----
def tool_with_context(x: int, ctx: Annotated[Context, "ctx"]) -> str
⋮----
def test_parameterized_union_context_parameter_detection(self)
⋮----
def tool_with_context(x: int, ctx: Context | None) -> str
⋮----
async def test_context_error_handling(self)
⋮----
"""Test error handling when context injection fails."""
⋮----
class TestCustomToolNames
⋮----
"""Test adding tools with custom names that differ from their function names."""
async def test_add_tool_with_custom_name(self)
⋮----
"""Test adding a tool with a custom name parameter using add_tool_from_fn."""
⋮----
tool = Tool.from_function(original_fn, name="custom_name")
⋮----
# The tool is stored under the custom name and its .name is also set to custom_name
⋮----
# The tool should not be accessible via its original function name
⋮----
async def test_add_tool_object_with_custom_key(self)
⋮----
"""Test adding a Tool object with a custom key using add_tool()."""
def fn(x: int) -> int
# Create a tool with a specific name
tool = Tool.from_function(fn, name="my_tool")
⋮----
# Use with_key to create a new tool with the custom key
tool_with_custom_key = tool.with_key("proxy_tool")
⋮----
# The tool is accessible under the key
stored = await manager.get_tool("proxy_tool")
⋮----
# But the tool's .name is unchanged
⋮----
# The tool is not accessible under its original name
⋮----
async def test_call_tool_with_custom_name(self)
⋮----
"""Test calling a tool added with a custom name."""
def multiply(a: int, b: int) -> int
⋮----
"""Multiply two numbers."""
⋮----
tool = Tool.from_function(multiply, name="custom_multiply")
⋮----
# Tool should be callable by its custom name
result = await manager.call_tool("custom_multiply", {"a": 5, "b": 3})
assert result.content[0].text == "15"  # type: ignore[attr-defined]
⋮----
# Original name should not be registered
⋮----
async def test_replace_tool_keeps_original_name(self)
⋮----
"""Test that replacing a tool with "replace" keeps the original name."""
⋮----
# Create a manager with REPLACE behavior
⋮----
# Add the original tool
original_tool = Tool.from_function(original_fn, name="test_tool")
⋮----
# Replace with a new function but keep the same registered name
replacement_tool = Tool.from_function(replacement_fn, name="test_tool")
⋮----
# The tool object should have been replaced
stored_tool = await manager.get_tool("test_tool")
⋮----
# The name should still be the same
⋮----
# But the function is different
⋮----
class TestToolErrorHandling
⋮----
"""Test error handling in the ToolManager."""
async def test_tool_error_passthrough(self)
⋮----
"""Test that ToolErrors are passed through directly."""
⋮----
def error_tool(x: int) -> int
⋮----
"""Tool that raises a ToolError."""
⋮----
async def test_exception_converted_to_tool_error_with_details(self)
⋮----
"""Test that other exceptions include details by default."""
⋮----
def buggy_tool(x: int) -> int
⋮----
"""Tool that raises a ValueError."""
⋮----
# Exception message should include the tool name and the internal details
⋮----
async def test_exception_converted_to_masked_tool_error(self)
⋮----
"""Test that other exceptions are masked when enabled."""
manager = ToolManager(mask_error_details=True)
⋮----
# Exception message should only contain the tool name, not the internal details
⋮----
async def test_async_tool_error_passthrough(self)
⋮----
"""Test that ToolErrors from async tools are passed through directly."""
⋮----
async def async_error_tool(x: int) -> int
⋮----
"""Async tool that raises a ToolError."""
⋮----
async def test_async_exception_converted_to_tool_error_with_details(self)
⋮----
"""Test that other exceptions from async tools include details by default."""
⋮----
async def async_buggy_tool(x: int) -> int
⋮----
"""Async tool that raises a ValueError."""
⋮----
async def test_async_exception_converted_to_masked_tool_error(self)
⋮----
"""Test that other exceptions from async tools are masked when enabled."""
⋮----
# Exception message should contain the tool name but not the internal details

================
File: tests/tools/test_tool_transform.py
================
def get_property(tool: Tool, name: str) -> dict[str, Any]
⋮----
@pytest.fixture
def add_tool() -> FunctionTool
def test_tool_from_tool_no_change(add_tool)
⋮----
new_tool = Tool.from_tool(add_tool)
⋮----
async def test_renamed_arg_description_is_maintained(add_tool)
⋮----
new_tool = Tool.from_tool(
⋮----
async def test_tool_defaults_are_maintained_on_unmapped_args(add_tool)
⋮----
result = await new_tool.run(arguments={"new_x": 1})
# The parent tool returns int which gets wrapped as structured output
⋮----
async def test_tool_defaults_are_maintained_on_mapped_args(add_tool)
⋮----
result = await new_tool.run(arguments={"old_x": 1})
⋮----
def test_tool_change_arg_name(add_tool)
def test_tool_change_arg_description(add_tool)
async def test_tool_drop_arg(add_tool)
async def test_dropped_args_error_if_provided(add_tool)
async def test_hidden_arg_with_constant_default(add_tool)
⋮----
"""Test that hidden argument with default value passes constant to parent."""
⋮----
# Only old_x should be exposed
⋮----
# Should pass old_x=5 and old_y=20 to parent
result = await new_tool.run(arguments={"old_x": 5})
⋮----
async def test_hidden_arg_without_default_uses_parent_default(add_tool)
⋮----
"""Test that hidden argument without default uses parent's default."""
⋮----
# Should pass old_x=3 and let parent use its default old_y=10
result = await new_tool.run(arguments={"old_x": 3})
assert result.content[0].text == "13"  # type: ignore[attr-defined]
⋮----
async def test_mixed_hidden_args_with_custom_function(add_tool)
⋮----
"""Test custom function with both hidden constant and hidden default parameters."""
async def custom_fn(visible_x: int) -> ToolResult
⋮----
# This custom function should receive the transformed visible parameter
# and the hidden parameters should be automatically handled
result = await forward(visible_x=visible_x)
⋮----
"old_x": ArgTransform(name="visible_x"),  # Rename and expose
"old_y": ArgTransform(hide=True, default=25),  # Hidden with constant
⋮----
# Only visible_x should be exposed
⋮----
# Should pass visible_x=7 as old_x=7 and old_y=25 to parent
result = await new_tool.run(arguments={"visible_x": 7})
assert result.content[0].text == "32"  # type: ignore[attr-defined]
⋮----
async def test_hide_required_param_without_default_raises_error()
⋮----
"""Test that hiding a required parameter without providing default raises error."""
⋮----
@Tool.from_function
    def tool_with_required_param(required_param: int, optional_param: int = 10) -> int
# This should raise an error because required_param has no default and we're not providing one
⋮----
async def test_hide_required_param_with_user_default_works()
⋮----
"""Test that hiding a required parameter works when user provides a default."""
⋮----
# This should work because we're providing a default for the hidden required param
⋮----
# Only optional_param should be exposed
⋮----
# Should pass required_param=5 and optional_param=20 to parent
result = await new_tool.run(arguments={"optional_param": 20})
⋮----
async def test_forward_with_argument_mapping(add_tool)
⋮----
"""Test that forward() applies argument mapping correctly."""
async def custom_fn(new_x: int, new_y: int = 5) -> ToolResult
⋮----
result = await new_tool.run(arguments={"new_x": 2, "new_y": 3})
assert result.content[0].text == "5"  # type: ignore[attr-defined]
⋮----
async def test_forward_with_incorrect_args_raises_error(add_tool)
⋮----
# the forward should use the new args, not the old ones
⋮----
async def test_forward_raw_without_argument_mapping(add_tool)
⋮----
"""Test that forward_raw() calls parent directly without mapping."""
⋮----
# Call parent directly with original argument names
result = await forward_raw(old_x=new_x, old_y=new_y)
⋮----
async def test_custom_fn_with_kwargs_and_no_transform_args(add_tool)
⋮----
async def custom_fn(extra: int, **kwargs) -> int
⋮----
sum = await forward(**kwargs)
return int(sum.content[0].text) + extra  # type: ignore[attr-defined]
new_tool = Tool.from_tool(add_tool, transform_fn=custom_fn)
result = await new_tool.run(arguments={"extra": 1, "old_x": 2, "old_y": 3})
assert result.content[0].text == "6"  # type: ignore[attr-defined]
⋮----
async def test_fn_with_kwargs_passes_through_original_args(add_tool)
⋮----
async def custom_fn(new_y: int = 5, **kwargs) -> ToolResult
⋮----
result = await forward(old_x=new_y, **kwargs)
⋮----
result = await new_tool.run(arguments={"new_y": 2, "old_y": 3})
⋮----
async def test_fn_with_kwargs_receives_transformed_arg_names(add_tool)
⋮----
"""Test that **kwargs receives arguments with their transformed names from transform_args."""
async def custom_fn(new_x: int, **kwargs) -> ToolResult
⋮----
# kwargs should contain 'old_y': 3 (transformed name), not 'old_y': 3 (original name)
⋮----
result = await forward(new_x=new_x, **kwargs)
⋮----
result = await new_tool.run(arguments={"new_x": 2, "old_y": 3})
⋮----
async def test_fn_with_kwargs_handles_partial_explicit_args(add_tool)
⋮----
"""Test that function can explicitly handle some transformed args while others pass through kwargs."""
⋮----
# x is explicitly handled, y should come through kwargs with transformed name
⋮----
result = await new_tool.run(
assert result.content[0].text == "10"  # type: ignore[attr-defined]
⋮----
async def test_fn_with_kwargs_mixed_mapped_and_unmapped_args(add_tool)
⋮----
"""Test **kwargs behavior with mix of mapped and unmapped arguments."""
⋮----
# new_x is explicitly handled, old_y should pass through kwargs with original name (unmapped)
⋮----
)  # only map 'a'
result = await new_tool.run(arguments={"new_x": 1, "old_y": 5})
⋮----
async def test_fn_with_kwargs_dropped_args_not_in_kwargs(add_tool)
⋮----
"""Test that dropped arguments don't appear in **kwargs."""
⋮----
# 'b' was dropped, so kwargs should be empty
⋮----
# Can't use 'old_y' since it was dropped, so just use 'old_x' mapped to 'new_x'
result = await forward(new_x=new_x)
⋮----
)  # drop 'old_y'
result = await new_tool.run(arguments={"new_x": 8})
# 8 + 10 (default value of b in parent)
assert result.content[0].text == "18"  # type: ignore[attr-defined]
async def test_forward_outside_context_raises_error()
⋮----
"""Test that forward() raises RuntimeError when called outside a transformed tool."""
⋮----
async def test_forward_raw_outside_context_raises_error()
⋮----
"""Test that forward_raw() raises RuntimeError when called outside a transformed tool."""
⋮----
def test_transform_args_validation_unknown_arg(add_tool)
⋮----
"""Test that transform_args with unknown arguments raises ValueError."""
⋮----
def test_transform_args_creates_duplicate_names(add_tool)
⋮----
"""Test that transform_args creating duplicate parameter names raises ValueError."""
⋮----
def test_function_without_kwargs_missing_params(add_tool)
⋮----
"""Test that function missing required transformed parameters raises ValueError."""
def invalid_fn(new_x: int, non_existent: str) -> str
⋮----
def test_function_without_kwargs_can_have_extra_params(add_tool)
⋮----
"""Test that function can have extra parameters not in parent tool."""
def valid_fn(new_x: int, new_y: int, extra_param: str = "default") -> str
# Should work - extra_param is fine as long as it has a default
⋮----
# The final schema should include all function parameters
⋮----
def test_function_with_kwargs_can_add_params(add_tool)
⋮----
"""Test that function with **kwargs can add new parameters."""
async def valid_fn(extra_param: str, **kwargs) -> str
⋮----
result = await forward(**kwargs)
⋮----
# This should work fine - kwargs allows access to all transformed params
tool = Tool.from_tool(
# extra_param is added, new_x and new_y are available
⋮----
async def test_tool_transform_chaining(add_tool)
⋮----
"""Test that transformed tools can be transformed again."""
# First transformation: a -> x
tool1 = Tool.from_tool(add_tool, transform_args={"old_x": ArgTransform(name="x")})
# Second transformation: x -> final_x, using tool1
tool2 = Tool.from_tool(tool1, transform_args={"x": ArgTransform(name="final_x")})
result = await tool2.run(arguments={"final_x": 5})
assert result.content[0].text == "15"  # type: ignore[attr-defined]
# Transform tool1 with custom function that handles all parameters
async def custom(final_x: int, **kwargs) -> str
⋮----
result = await forward(final_x=final_x, **kwargs)
return f"custom {result.content[0].text}"  # Extract text from content # type: ignore[attr-defined]
tool3 = Tool.from_tool(
result = await tool3.run(arguments={"final_x": 3, "old_y": 5})
assert result.content[0].text == "custom 8"  # type: ignore[attr-defined]
class MyModel(BaseModel)
⋮----
x: int
y: str
⋮----
@dataclass
class MyDataclass
class MyTypedDict(TypedDict)
⋮----
def test_arg_transform_type_handling(add_tool, py_type, json_type)
⋮----
"""Test that ArgTransform type attribute gets applied to schema."""
⋮----
# Check that the type was changed in the schema
x_prop = get_property(new_tool, "old_x")
⋮----
def test_arg_transform_annotated_types(add_tool)
⋮----
"""Test that ArgTransform works with annotated types and complex types."""
⋮----
# Test with Annotated types
⋮----
x_prop = get_property(tool, "old_x")
⋮----
# The ArgTransform description should override the annotation description
# (since we didn't set a description in ArgTransform, it should use the original)
# Test with Annotated string that has constraints
tool2 = Tool.from_tool(
x_prop2 = get_property(tool2, "old_x")
⋮----
def test_arg_transform_precedence_over_function_without_kwargs()
⋮----
"""Test that ArgTransform attributes take precedence over function signature (no **kwargs)."""
⋮----
@Tool.from_function
    def base(x: int, y: str = "default") -> str
# Function signature says x: int with no default, y: str = "function_default"
# ArgTransform should override these
def custom_fn(x: str = "transform_default", y: int = 99) -> str
⋮----
# ArgTransform should take precedence
x_prop = get_property(tool, "x")
y_prop = get_property(tool, "y")
assert x_prop["type"] == "string"  # ArgTransform type wins
assert x_prop["default"] == "transform_default"  # ArgTransform default wins
assert y_prop["type"] == "integer"  # ArgTransform type wins
assert y_prop["default"] == 99  # ArgTransform default wins
# Neither parameter should be required due to ArgTransform defaults
⋮----
async def test_arg_transform_precedence_over_function_with_kwargs()
⋮----
"""Test that ArgTransform attributes take precedence over function signature (with **kwargs)."""
⋮----
@Tool.from_function
    def base(x: int, y: str = "base_default") -> str
# Function signature has different types/defaults than ArgTransform
async def custom_fn(x: str = "function_default", **kwargs) -> str
⋮----
result = await forward(x=x, **kwargs)
return f"custom: {result.content[0].text}"  # type: ignore[attr-defined]
⋮----
"x": ArgTransform(type=int, default=42),  # Different type and default
⋮----
assert x_prop["type"] == "integer"  # ArgTransform type wins over function's str
assert x_prop["default"] == 42  # ArgTransform default wins over function's default
⋮----
)  # ArgTransform description
# x should not be required due to ArgTransform default
⋮----
# Test it works at runtime
result = await tool.run(arguments={"y": "test"})
# Should use ArgTransform default of 42
assert "42: test" in result.content[0].text  # type: ignore[attr-defined]
def test_arg_transform_combined_attributes()
⋮----
"""Test that multiple ArgTransform attributes work together."""
⋮----
@Tool.from_function
    def base(param: int) -> str
⋮----
# Check all attributes were applied
⋮----
prop = get_property(tool, "renamed_param")
⋮----
assert "renamed_param" not in tool.parameters["required"]  # Has default
async def test_arg_transform_type_precedence_runtime()
⋮----
"""Test that ArgTransform type changes work correctly at runtime."""
⋮----
@Tool.from_function
    def base(x: int, y: int = 10) -> int
# Transform x to string type but keep same logic
async def custom_fn(x: str, y: int = 10) -> str
⋮----
# Convert string back to int for the original function
result = await forward_raw(x=int(x), y=y)
# Extract the text from the result
result_text = result.content[0].text  # type: ignore[attr-defined]
⋮----
# Verify schema shows string type
⋮----
# Test it works with string input
result = await tool.run(arguments={"x": "5", "y": 3})
assert "String input '5'" in result.content[0].text  # type: ignore[attr-defined]
assert "result: 8" in result.content[0].text  # type: ignore[attr-defined]
class TestProxy
⋮----
@pytest.fixture
    def mcp_server(self) -> FastMCP
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
        def add(old_x: int, old_y: int = 10) -> int
⋮----
@pytest.fixture
    def proxy_server(self, mcp_server: FastMCP) -> FastMCP
⋮----
proxy = FastMCP.as_proxy(Client(transport=FastMCPTransport(mcp_server)))
⋮----
async def test_transform_proxy(self, proxy_server: FastMCP)
⋮----
# when adding transformed tools to proxy servers. Needs separate investigation.
add_tool = await proxy_server.get_tool("add")
new_add_tool = Tool.from_tool(
⋮----
# The tool should be registered with its transformed name
result = await client.call_tool("add_transformed", {"new_x": 1, "old_y": 2})
assert result.content[0].text == "3"  # type: ignore[attr-defined]
async def test_arg_transform_default_factory()
⋮----
"""Test ArgTransform with default_factory for hidden parameters."""
⋮----
@Tool.from_function
    def base_tool(x: int, timestamp: float) -> str
# Create a tool with default_factory for hidden timestamp
⋮----
# Only x should be visible since timestamp is hidden
⋮----
# Should work without providing timestamp (gets value from factory)
result = await new_tool.run(arguments={"x": 42})
assert result.content[0].text == "42_12345.0"  # type: ignore[attr-defined]
async def test_arg_transform_default_factory_called_each_time()
⋮----
"""Test that default_factory is called for each execution."""
call_count = 0
def counter_factory()
⋮----
@Tool.from_function
    def base_tool(x: int, counter: int = 0) -> str
⋮----
# Only x should be visible since counter is hidden
⋮----
# First call
result1 = await new_tool.run(arguments={"x": 1})
assert result1.content[0].text == "1_1"  # type: ignore[attr-defined]
# Second call should get a different value
result2 = await new_tool.run(arguments={"x": 2})
assert result2.content[0].text == "2_2"  # type: ignore[attr-defined]
async def test_arg_transform_hidden_with_default_factory()
⋮----
"""Test hidden parameter with default_factory."""
⋮----
@Tool.from_function
    def base_tool(x: int, request_id: str) -> str
def make_request_id()
⋮----
# Only x should be visible
⋮----
# Should pass hidden request_id with factory value
⋮----
assert result.content[0].text == "42_req_123"  # type: ignore[attr-defined]
async def test_arg_transform_default_and_factory_raises_error()
⋮----
"""Test that providing both default and default_factory raises an error."""
⋮----
async def test_arg_transform_default_factory_requires_hide()
⋮----
"""Test that default_factory requires hide=True."""
⋮----
ArgTransform(default_factory=lambda: 42)  # hide=False by default
async def test_arg_transform_required_true()
⋮----
"""Test that required=True makes an optional parameter required."""
⋮----
@Tool.from_function
    def base_tool(optional_param: int = 42) -> str
# Make the optional parameter required
⋮----
# Parameter should now be required (no default in schema)
⋮----
# Should work when parameter is provided
result = await new_tool.run(arguments={"optional_param": 100})
assert result.content[0].text == "value: 100"  # type: ignore
# Should fail when parameter is not provided
⋮----
async def test_arg_transform_required_false()
⋮----
"""Test that required=False makes a required parameter optional with default."""
⋮----
@Tool.from_function
    def base_tool(required_param: int) -> str
⋮----
transform_args={"required_param": ArgTransform(required=False, default=99)},  # type: ignore
⋮----
async def test_arg_transform_required_with_rename()
⋮----
"""Test that required works correctly with argument renaming."""
⋮----
# Rename and make required
⋮----
# New parameter name should be required
⋮----
# Should work with new name
result = await new_tool.run(arguments={"new_param": 200})
assert result.content[0].text == "value: 200"  # type: ignore
async def test_arg_transform_required_true_with_default_raises_error()
⋮----
"""Test that required=True with default raises an error."""
⋮----
async def test_arg_transform_required_true_with_factory_raises_error()
⋮----
"""Test that required=True with default_factory raises an error."""
⋮----
async def test_arg_transform_required_no_change()
⋮----
"""Test that required=... (NotSet) leaves requirement status unchanged."""
⋮----
@Tool.from_function
    def base_tool(required_param: int, optional_param: int = 42) -> str
# Transform without changing required status
⋮----
# Required status should be unchanged
⋮----
# Should work as expected
result = await new_tool.run(arguments={"req": 1})
assert result.content[0].text == "values: 1, 42"  # type: ignore
async def test_arg_transform_hide_and_required_raises_error()
⋮----
"""Test that hide=True and required=True together raises an error."""
⋮----
class TestEnableDisable
⋮----
async def test_transform_disabled_tool(self)
⋮----
"""
        Tests that a transformed tool can run even if the parent tool is disabled
        """
⋮----
@mcp.tool(enabled=False)
        def add(x: int, y: int = 10) -> int
new_add = Tool.from_tool(add, name="new_add")
⋮----
tools = await client.list_tools()
⋮----
result = await client.call_tool("new_add", {"x": 1, "y": 2})
⋮----
async def test_disable_transformed_tool(self)
⋮----
new_add = Tool.from_tool(add, name="new_add", enabled=False)
⋮----
def test_arg_transform_examples_in_schema(add_tool)
⋮----
# Simple example
⋮----
prop = get_property(new_tool, "old_x")
⋮----
# Nested example (e.g., for array type)
new_tool2 = Tool.from_tool(
prop2 = get_property(new_tool2, "old_x")
⋮----
# If not set, should not be present
new_tool3 = Tool.from_tool(
prop3 = get_property(new_tool3, "old_x")
⋮----
class TestTransformToolOutputSchema
⋮----
"""Test output schema handling in transformed tools."""
⋮----
@pytest.fixture
    def base_string_tool(self) -> FunctionTool
⋮----
"""Tool that returns a string (gets wrapped)."""
def string_tool(x: int) -> str
⋮----
@pytest.fixture
    def base_dict_tool(self) -> FunctionTool
⋮----
"""Tool that returns a dict (object type, not wrapped)."""
def dict_tool(x: int) -> dict[str, int]
⋮----
def test_transform_inherits_parent_output_schema(self, base_string_tool)
⋮----
"""Test that transformed tool inherits parent's output schema by default."""
new_tool = Tool.from_tool(base_string_tool)
# Should inherit parent's wrapped string schema
expected_schema = {
⋮----
def test_transform_with_explicit_output_schema_false(self, base_string_tool)
⋮----
"""Test that output_schema=False disables structured output."""
new_tool = Tool.from_tool(base_string_tool, output_schema=False)
⋮----
async def test_transform_output_schema_false_runtime(self, base_string_tool)
⋮----
"""Test runtime behavior with output_schema=False."""
⋮----
# Debug: check that output_schema is actually None
⋮----
result = await new_tool.run({"x": 5})
⋮----
assert result.content[0].text == "Result: 5"  # type: ignore[attr-defined]
def test_transform_with_explicit_output_schema_dict(self, base_string_tool)
⋮----
"""Test that explicit output schema overrides parent."""
custom_schema = {
new_tool = Tool.from_tool(base_string_tool, output_schema=custom_schema)
⋮----
async def test_transform_explicit_schema_runtime(self, base_string_tool)
⋮----
"""Test runtime behavior with explicit output schema."""
custom_schema = {"type": "string", "minLength": 1}
⋮----
result = await new_tool.run({"x": 10})
# Non-object explicit schemas disable structured content
⋮----
assert result.content[0].text == "Result: 10"  # type: ignore[attr-defined]
def test_transform_with_custom_function_inferred_schema(self, base_dict_tool)
⋮----
"""Test that custom function's output schema is inferred."""
async def custom_fn(x: int) -> str
⋮----
result = await forward(x=x)
return f"Custom: {result.content[0].text}"  # type: ignore[attr-defined]
new_tool = Tool.from_tool(base_dict_tool, transform_fn=custom_fn)
# Should infer string schema from custom function and wrap it
⋮----
async def test_transform_custom_function_runtime(self, base_dict_tool)
⋮----
"""Test runtime behavior with custom function that has inferred schema."""
⋮----
result = await new_tool.run({"x": 3})
# Should wrap string result
⋮----
def test_transform_custom_function_fallback_to_parent(self, base_string_tool)
⋮----
"""Test that custom function without output annotation falls back to parent."""
async def custom_fn(x: int)
⋮----
# No return annotation - should fallback to parent schema
⋮----
new_tool = Tool.from_tool(base_string_tool, transform_fn=custom_fn)
# Should use parent's schema since custom function has no annotation
⋮----
def test_transform_custom_function_explicit_overrides(self, base_string_tool)
⋮----
"""Test that explicit output_schema overrides both custom function and parent."""
async def custom_fn(x: int) -> dict[str, str]
explicit_schema = {"type": "array", "items": {"type": "number"}}
⋮----
# Explicit schema should win
⋮----
async def test_transform_custom_function_object_return(self, base_string_tool)
⋮----
"""Test custom function returning object type."""
async def custom_fn(x: int) -> dict[str, int]
⋮----
# Object types should not be wrapped
expected_schema = TypeAdapter(dict[str, int]).json_schema()
⋮----
assert "x-fastmcp-wrap-result" not in new_tool.output_schema  # type: ignore[attr-defined]
result = await new_tool.run({"x": 4})
# Direct value, not wrapped
⋮----
async def test_transform_preserves_wrap_marker_behavior(self, base_string_tool)
⋮----
"""Test that wrap marker behavior is preserved through transformation."""
⋮----
result = await new_tool.run({"x": 7})
# Should wrap because parent schema has wrap marker
⋮----
assert "x-fastmcp-wrap-result" in new_tool.output_schema  # type: ignore[attr-defined]
def test_transform_chained_output_schema_inheritance(self, base_string_tool)
⋮----
"""Test output schema inheritance through multiple transformations."""
# First transformation keeps parent schema
tool1 = Tool.from_tool(base_string_tool)
⋮----
# Second transformation also inherits
tool2 = Tool.from_tool(tool1)
⋮----
# Third transformation with explicit override
custom_schema = {"type": "number"}
tool3 = Tool.from_tool(tool2, output_schema=custom_schema)
⋮----
"""Test transformation handling of mixed content types."""
⋮----
# Return mixed content including ToolResult
⋮----
# Return ToolResult directly
⋮----
# Test mixed content return
result1 = await new_tool.run({"x": 1})
⋮----
# Test ToolResult return
result2 = await new_tool.run({"x": 2})
⋮----
assert result2.content[0].text == "Custom: 2"  # type: ignore[attr-defined]
def test_transform_output_schema_with_arg_transforms(self, base_string_tool)
⋮----
"""Test that output schema works correctly with argument transformations."""
async def custom_fn(new_x: int) -> dict[str, str]
⋮----
return {"transformed": result.content[0].text}  # type: ignore[attr-defined]
⋮----
# Should infer object schema from custom function
expected_schema = TypeAdapter(dict[str, str]).json_schema()
⋮----
async def test_transform_output_schema_none_vs_false(self, base_string_tool)
⋮----
"""Test None vs False behavior for output_schema in transforms."""
# None (default) should use smart fallback (inherit from parent)
tool_none = Tool.from_tool(base_string_tool)  # default output_schema=None
assert tool_none.output_schema == base_string_tool.output_schema  # Inherits
# False should explicitly disable
tool_false = Tool.from_tool(base_string_tool, output_schema=False)
⋮----
# Different behavior at runtime
result_none = await tool_none.run({"x": 5})
result_false = await tool_false.run({"x": 5})
⋮----
}  # Inherits wrapping
assert result_false.structured_content is None  # Disabled
assert result_none.content[0].text == result_false.content[0].text  # type: ignore[attr-defined]
⋮----
"""Test transform when custom function returns ToolResult directly."""
async def custom_fn(x: int) -> ToolResult
⋮----
# Custom function returns ToolResult - should bypass schema handling
⋮----
# ToolResult return type should result in None output schema
⋮----
result = await new_tool.run({"x": 6})
# Should use ToolResult content directly
assert result.content[0].text == "Direct: 6"  # type: ignore[attr-defined]

================
File: tests/tools/test_tool.py
================
class TestToolFromFunction
⋮----
def test_basic_function(self)
⋮----
"""Test registering and running a basic function."""
def add(a: int, b: int) -> int
⋮----
"""Add two numbers."""
⋮----
tool = Tool.from_function(add)
⋮----
# With primitive wrapping, int return type becomes object with result property
expected_schema = {
⋮----
async def test_async_function(self)
⋮----
"""Test registering and running an async function."""
async def fetch_data(url: str) -> str
⋮----
"""Fetch data from URL."""
⋮----
tool = Tool.from_function(fetch_data)
⋮----
def test_callable_object(self)
⋮----
class Adder
⋮----
"""Adds two numbers."""
def __call__(self, x: int, y: int) -> int
⋮----
"""ignore this"""
⋮----
tool = Tool.from_function(Adder())
⋮----
def test_async_callable_object(self)
⋮----
async def __call__(self, x: int, y: int) -> int
⋮----
def test_pydantic_model_function(self)
⋮----
"""Test registering a function that takes a Pydantic model."""
class UserInput(BaseModel)
⋮----
name: str
age: int
def create_user(user: UserInput, flag: bool) -> dict
⋮----
"""Create a new user."""
⋮----
tool = Tool.from_function(create_user)
⋮----
async def test_tool_with_image_return(self)
⋮----
def image_tool(data: bytes) -> Image
tool = Tool.from_function(image_tool)
result = await tool.run({"data": "test.png"})
⋮----
async def test_tool_with_audio_return(self)
⋮----
def audio_tool(data: bytes) -> Audio
tool = Tool.from_function(audio_tool)
result = await tool.run({"data": "test.wav"})
⋮----
async def test_tool_with_file_return(self)
⋮----
def file_tool(data: bytes) -> File
tool = Tool.from_function(file_tool)
result = await tool.run({"data": "test.bin"})
⋮----
resource = result.content[0].resource
⋮----
def test_non_callable_fn(self)
⋮----
Tool.from_function(1)  # type: ignore
def test_lambda(self)
⋮----
tool = Tool.from_function(lambda x: x, name="my_tool")
⋮----
def test_lambda_with_no_name(self)
def test_private_arguments(self)
⋮----
def add(_a: int, _b: int) -> int
⋮----
def test_tool_with_varargs_not_allowed(self)
⋮----
def func(a: int, b: int, *args: int) -> int
⋮----
def test_tool_with_varkwargs_not_allowed(self)
⋮----
def func(a: int, b: int, **kwargs: int) -> int
⋮----
async def test_instance_method(self)
⋮----
class MyClass
⋮----
def add(self, x: int, y: int) -> int
⋮----
"""Add two numbers."""
⋮----
obj = MyClass()
tool = Tool.from_function(obj.add)
⋮----
async def test_instance_method_with_varargs_not_allowed(self)
⋮----
def add(self, x: int, y: int, *args: int) -> int
⋮----
async def test_instance_method_with_varkwargs_not_allowed(self)
⋮----
def add(self, x: int, y: int, **kwargs: int) -> int
⋮----
async def test_classmethod(self)
⋮----
x: int = 10
⋮----
@classmethod
            def call(cls, x: int, y: int) -> int
tool = Tool.from_function(MyClass.call)
⋮----
async def test_tool_serializer(self)
⋮----
"""Test that a tool's serializer is used to serialize the result."""
def custom_serializer(data) -> str
def process_list(items: list[int]) -> int
tool = Tool.from_function(process_list, serializer=custom_serializer)
result = await tool.run(arguments={"items": [1, 2, 3, 4, 5]})
# Custom serializer affects unstructured content
⋮----
# Structured output should have the raw value
⋮----
class TestToolFromFunctionOutputSchema
⋮----
async def test_no_return_annotation(self)
⋮----
def func()
tool = Tool.from_function(func)
⋮----
async def test_simple_return_annotation(self, annotation)
⋮----
def func() -> annotation:  # type: ignore
⋮----
base_schema = TypeAdapter(annotation).json_schema()
# Non-object types get wrapped
schema_type = base_schema.get("type")
is_object_type = schema_type == "object"
⋮----
# Non-object types get wrapped
⋮----
# Object types remain unwrapped
⋮----
async def test_complex_return_annotation(self, annotation)
async def test_none_return_annotation(self)
⋮----
def func() -> None
⋮----
async def test_any_return_annotation(self)
⋮----
def func() -> Any
⋮----
async def test_converted_return_annotation(self, annotation, expected)
⋮----
# Image, Audio, File types don't generate output schemas since they're converted to content directly
⋮----
async def test_dataclass_return_annotation(self)
⋮----
@dataclass
        class Person
def func() -> Person
⋮----
async def test_base_model_return_annotation(self)
⋮----
class Person(BaseModel)
⋮----
async def test_typeddict_return_annotation(self)
⋮----
class Person(TypedDict)
⋮----
async def test_unserializable_return_annotation(self)
⋮----
class Unserializable
⋮----
def __init__(self, data: Any)
def func() -> Unserializable
⋮----
async def test_mixed_unserializable_return_annotation(self)
⋮----
def func() -> Unserializable | int
⋮----
"""Test that provided output_schema takes precedence over inferred schema from JSON-compatible annotation."""
def func() -> dict[str, int]
# Provide a custom output schema that differs from the inferred one
custom_schema = {"type": "object", "description": "Custom schema"}
tool = Tool.from_function(func, output_schema=custom_schema)
⋮----
"""Test that provided output_schema takes precedence over inferred schema from complex annotation."""
def func() -> list[dict[str, int | float]]
⋮----
custom_schema = {"type": "object", "properties": {"custom": {"type": "string"}}}
⋮----
"""Test that provided output_schema takes precedence over None schema from unserializable annotation."""
⋮----
# Provide a custom output schema even though the annotation is unserializable
custom_schema = {
⋮----
async def test_provided_output_schema_takes_precedence_over_no_annotation(self)
⋮----
"""Test that provided output_schema takes precedence over None schema from no annotation."""
⋮----
# Provide a custom output schema even though there's no return annotation
⋮----
"""Test that provided output_schema takes precedence over converted schema from Image/Audio/File annotations."""
def func() -> Image
# Provide a custom output schema that differs from the converted ImageContent schema
⋮----
async def test_provided_output_schema_takes_precedence_over_union_annotation(self)
⋮----
"""Test that provided output_schema takes precedence over inferred schema from union annotation."""
def func() -> str | int | None
# Provide a custom output schema that differs from the inferred union schema
custom_schema = {"type": "object", "properties": {"flag": {"type": "boolean"}}}
⋮----
"""Test that provided output_schema takes precedence over inferred schema from Pydantic model annotation."""
⋮----
# Provide a custom output schema that differs from the inferred Person schema
⋮----
async def test_output_schema_false_allows_automatic_structured_content(self)
⋮----
"""Test that output_schema=False still allows automatic structured content for dict-like objects."""
def func() -> dict[str, str]
tool = Tool.from_function(func, output_schema=False)
⋮----
result = await tool.run({})
# Dict objects automatically become structured content even without schema
⋮----
assert result.content[0].text == '{\n  "message": "Hello, world!"\n}'  # type: ignore[attr-defined]
async def test_output_schema_none_disables_structured_content(self)
⋮----
"""Test that output_schema=None explicitly disables structured content."""
def func() -> int
tool = Tool.from_function(func, output_schema=None)
⋮----
assert result.content[0].text == "42"  # type: ignore[attr-defined]
async def test_output_schema_inferred_when_not_specified(self)
⋮----
"""Test that output schema is inferred when not explicitly specified."""
⋮----
# Don't specify output_schema - should infer and wrap
⋮----
async def test_explicit_object_schema_with_dict_return(self)
⋮----
"""Test that explicit object schemas work when function returns a dict."""
⋮----
# Provide explicit object schema
explicit_schema = {
tool = Tool.from_function(func, output_schema=explicit_schema)
assert tool.output_schema == explicit_schema  # Schema not wrapped
⋮----
# Dict result with object schema is used directly
⋮----
assert result.content[0].text == '{\n  "value": 42\n}'  # type: ignore[attr-defined]
async def test_explicit_object_schema_with_non_dict_return_fails(self)
⋮----
"""Test that explicit object schemas fail when function returns non-dict."""
⋮----
# Provide explicit object schema but return non-dict
⋮----
# Should fail because int is not dict-compatible with object schema
⋮----
async def test_object_output_schema_not_wrapped(self)
⋮----
"""Test that object-type output schemas are never wrapped."""
⋮----
# Object schemas should never be wrapped, even when inferred
⋮----
expected_schema = TypeAdapter(dict[str, int]).json_schema()
assert tool.output_schema == expected_schema  # Not wrapped
⋮----
assert result.structured_content == {"value": 42}  # Direct value
async def test_structured_content_interaction_with_wrapping(self)
⋮----
"""Test that structured content works correctly with schema wrapping."""
def func() -> str
# Inferred schema should wrap string type
⋮----
# Unstructured content
⋮----
assert result.content[0].text == "hello"  # type: ignore[attr-defined]
# Structured content should be wrapped
⋮----
async def test_structured_content_with_explicit_object_schema(self)
⋮----
"""Test structured content with explicit object schema."""
⋮----
# Should use direct value since explicit schema doesn't have wrap marker
⋮----
async def test_structured_content_with_custom_wrapper_schema(self)
⋮----
"""Test structured content with custom schema that includes wrap marker."""
⋮----
# Custom schema with wrap marker
⋮----
# Should wrap with "result" key due to wrap marker
⋮----
async def test_none_vs_false_output_schema_behavior(self)
⋮----
"""Test the difference between None and False for output_schema."""
⋮----
# None should disable
tool_none = Tool.from_function(func, output_schema=None)
⋮----
# False should also disable
tool_false = Tool.from_function(func, output_schema=False)
⋮----
# Both should have same behavior
result_none = await tool_none.run({})
result_false = await tool_false.run({})
⋮----
assert result_none.content[0].text == result_false.content[0].text == "123"  # type: ignore[attr-defined]
async def test_non_object_output_schema_raises_error(self)
⋮----
"""Test that providing a non-object output schema raises a ValueError."""
⋮----
# Test various non-object schemas that should raise errors
non_object_schemas = [
⋮----
class TestConvertResultToContent
⋮----
"""Tests for the _convert_to_content helper function."""
def test_none_result(self)
⋮----
"""Test that None results in an empty list."""
result = _convert_to_content(None)
⋮----
def test_text_content_result(self)
⋮----
"""Test that TextContent is returned as a list containing itself."""
content = TextContent(type="text", text="hello")
result = _convert_to_content(content)
⋮----
def test_image_content_result(self)
⋮----
"""Test that ImageContent is returned as a list containing itself."""
content = ImageContent(type="image", data="fakeimagedata", mimeType="image/png")
⋮----
def test_embedded_resource_result(self)
⋮----
"""Test that EmbeddedResource is returned as a list containing itself."""
content = EmbeddedResource(
⋮----
def test_image_object_result(self)
⋮----
"""Test that an Image object is converted to ImageContent."""
image_obj = Image(data=b"fakeimagedata")
result = _convert_to_content(image_obj)
⋮----
def test_audio_object_result(self)
⋮----
"""Test that an Audio object is converted to AudioContent."""
audio_obj = Audio(data=b"fakeaudiodata")
result = _convert_to_content(audio_obj)
⋮----
def test_file_object_result(self)
⋮----
"""Test that a File object is converted to EmbeddedResource with BlobResourceContents."""
file_obj = File(data=b"filedata", format="octet-stream")
result = _convert_to_content(file_obj)
⋮----
resource = result[0].resource
⋮----
# Check for blob attribute and its value
⋮----
assert getattr(resource, "blob") == "ZmlsZWRhdGE="  # base64 encoded "filedata"
# Convert URI to string for startswith check
⋮----
def test_file_object_text_result(self)
⋮----
"""Test that a File object with text data is converted to EmbeddedResource with TextResourceContents."""
file_obj = File(data=b"sometext", format="plain")
⋮----
def test_basic_type_result(self)
⋮----
"""Test that a basic type is converted to TextContent."""
result = _convert_to_content(123)
⋮----
result = _convert_to_content("hello")
⋮----
result = _convert_to_content({"a": 1, "b": 2})
⋮----
def test_list_of_basic_types(self)
⋮----
"""Test that a list of basic types is converted to a single TextContent."""
result = _convert_to_content([1, "two", {"c": 3}])
⋮----
def test_list_of_mcp_types(self)
⋮----
"""Test that a list of MCP types is returned as a list of those types."""
content1 = TextContent(type="text", text="hello")
content2 = ImageContent(
result = _convert_to_content([content1, content2])
⋮----
def test_list_of_mixed_types(self)
⋮----
"""Test that a list of mixed types is converted correctly."""
⋮----
basic_data = {"a": 1}
result = _convert_to_content([content1, image_obj, basic_data])
⋮----
text_content_count = sum(isinstance(item, TextContent) for item in result)
image_content_count = sum(isinstance(item, ImageContent) for item in result)
⋮----
text_item = next(item for item in result if isinstance(item, TextContent))
⋮----
image_item = next(item for item in result if isinstance(item, ImageContent))
⋮----
def test_list_of_mixed_types_list(self)
⋮----
"""Test that a list of mixed types, including a list as one of the elements, is converted correctly."""
⋮----
basic_data = [{"a": 1}, {"b": 2}]
⋮----
def test_list_of_mixed_types_with_audio(self)
⋮----
"""Test that a list of mixed types including Audio is converted correctly."""
⋮----
result = _convert_to_content([content1, audio_obj, basic_data])
⋮----
audio_content_count = sum(isinstance(item, AudioContent) for item in result)
⋮----
audio_item = next(item for item in result if isinstance(item, AudioContent))
⋮----
def test_list_of_mixed_types_with_file(self)
⋮----
"""Test that a list of mixed types including File is converted correctly."""
⋮----
result = _convert_to_content([content1, file_obj, basic_data])
⋮----
embedded_content_count = sum(
⋮----
embedded_item = next(
resource = embedded_item.resource
⋮----
def test_empty_list(self)
⋮----
"""Test that an empty list results in an empty list."""
result = _convert_to_content([])
⋮----
def test_empty_dict(self)
⋮----
"""Test that an empty dictionary is converted to TextContent."""
result = _convert_to_content({})
⋮----
def test_with_custom_serializer(self)
⋮----
"""Test that a custom serializer is used for non-MCP types."""
def custom_serializer(data)
result = _convert_to_content({"a": 1}, serializer=custom_serializer)
⋮----
def test_custom_serializer_error_fallback(self, caplog)
⋮----
"""Test that if a custom serializer fails, it falls back to the default."""
⋮----
def custom_serializer_that_fails(data)
⋮----
result = _convert_to_content(
⋮----
# Should fall back to default serializer (pydantic_core.to_json)
⋮----
def test_process_as_single_item_flag(self)
⋮----
"""Test that _process_as_single_item forces list to be treated as one item."""
result = _convert_to_content([1, "two", {"c": 3}], _process_as_single_item=True)
⋮----
result = _convert_to_content([1, content1], _process_as_single_item=True)
⋮----
class TestAutomaticStructuredContent
⋮----
"""Tests for automatic structured content generation based on return types."""
async def test_dict_return_creates_structured_content_without_schema(self)
⋮----
"""Test that dict returns automatically create structured content even without output schema."""
def get_user_data(user_id: str) -> dict
# No explicit output schema provided
tool = Tool.from_function(get_user_data)
result = await tool.run({"user_id": "123"})
# Should have both content and structured content
⋮----
async def test_dataclass_return_creates_structured_content_without_schema(self)
⋮----
"""Test that dataclass returns automatically create structured content even without output schema."""
⋮----
@dataclass
        class UserProfile
⋮----
email: str
def get_profile(user_id: str) -> UserProfile
# No explicit output schema, but dataclass should still create structured content
tool = Tool.from_function(get_profile, output_schema=False)
result = await tool.run({"user_id": "456"})
⋮----
# Dataclass should serialize to dict
⋮----
"""Test that Pydantic model returns automatically create structured content even without output schema."""
class UserData(BaseModel)
⋮----
username: str
score: int
verified: bool
def get_user_stats(user_id: str) -> UserData
# Explicitly disable output schema to test automatic structured content
tool = Tool.from_function(get_user_stats, output_schema=False)
result = await tool.run({"user_id": "789"})
⋮----
# Pydantic model should serialize to dict
⋮----
async def test_int_return_no_structured_content_without_schema(self)
⋮----
"""Test that int returns don't create structured content without output schema."""
def calculate_sum(a: int, b: int)
⋮----
"""No return annotation."""
⋮----
# No output schema
tool = Tool.from_function(calculate_sum)
result = await tool.run({"a": 5, "b": 3})
# Should only have content, no structured content
⋮----
async def test_str_return_no_structured_content_without_schema(self)
⋮----
"""Test that str returns don't create structured content without output schema."""
def get_greeting(name: str)
⋮----
tool = Tool.from_function(get_greeting)
result = await tool.run({"name": "World"})
⋮----
async def test_list_return_no_structured_content_without_schema(self)
⋮----
"""Test that list returns don't create structured content without output schema."""
def get_numbers()
⋮----
tool = Tool.from_function(get_numbers)
⋮----
async def test_int_return_with_schema_creates_structured_content(self)
⋮----
"""Test that int returns DO create structured content when there's an output schema."""
def calculate_sum(a: int, b: int) -> int
⋮----
"""With return annotation."""
⋮----
# Output schema should be auto-generated from annotation
⋮----
async def test_client_automatic_deserialization_with_dict_result(self)
⋮----
"""Test that clients automatically deserialize dict results from structured content."""
⋮----
mcp = FastMCP()
⋮----
@mcp.tool
        def get_user_info(user_id: str) -> dict
⋮----
result = await client.call_tool("get_user_info", {"user_id": "123"})
# Client should provide the deserialized data
⋮----
async def test_client_automatic_deserialization_with_dataclass_result(self)
⋮----
"""Test that clients automatically deserialize dataclass results from structured content."""
⋮----
@mcp.tool
        def get_profile(user_id: str) -> UserProfile
⋮----
result = await client.call_tool("get_profile", {"user_id": "456"})
# Client should deserialize back to a dataclass (type name will match)
⋮----
class TestToolTitle
⋮----
"""Tests for tool title functionality."""
def test_tool_with_title(self)
⋮----
"""Test that tools can have titles and they appear in MCP conversion."""
def calculate(x: int, y: int) -> int
⋮----
"""Calculate the sum of two numbers."""
⋮----
tool = Tool.from_function(
⋮----
# Test MCP conversion includes title
mcp_tool = tool.to_mcp_tool()
⋮----
def test_tool_without_title(self)
⋮----
"""Test that tools without titles use name as display name."""
def multiply(a: int, b: int) -> int
tool = Tool.from_function(multiply)
⋮----
# Test MCP conversion doesn't include title when None
⋮----
def test_tool_title_priority(self)
⋮----
"""Test that explicit title takes priority over annotations.title."""
⋮----
def divide(x: int, y: int) -> float
⋮----
"""Divide two numbers."""
⋮----
# Test with both explicit title and annotations.title
annotations = ToolAnnotations(title="Annotation Title")
⋮----
# Explicit title should take priority
⋮----
def test_tool_annotations_title_fallback(self)
⋮----
"""Test that annotations.title is used when no explicit title is provided."""
⋮----
def modulo(x: int, y: int) -> int
⋮----
"""Get modulo of two numbers."""
⋮----
# Test with only annotations.title (no explicit title)
⋮----
# Should fall back to annotations.title

================
File: tests/utilities/openapi/__init__.py
================
"""Tests for the OpenAPI utilities."""

================
File: tests/utilities/openapi/conftest.py
================


================
File: tests/utilities/openapi/test_openapi_advanced.py
================
"""Tests for advanced features of the OpenAPI utilities."""
⋮----
@pytest.fixture
def complex_schema() -> dict[str, Any]
⋮----
"""Fixture that returns a complex OpenAPI schema with nested references."""
⋮----
@pytest.fixture
def parsed_complex_routes(complex_schema)
⋮----
"""Return parsed routes from the complex schema."""
⋮----
@pytest.fixture
def complex_route_map(parsed_complex_routes)
⋮----
"""Return a dictionary of routes by operation ID."""
⋮----
@pytest.fixture
def schema_with_invalid_reference() -> dict[str, Any]
⋮----
"""Fixture that returns a schema with an invalid reference."""
⋮----
"parameters": {}  # Empty parameters object to ensure the reference is broken
⋮----
@pytest.fixture
def schema_with_content_params() -> dict[str, Any]
⋮----
"""Fixture that returns a schema with content-based parameters (complex parameters)."""
⋮----
@pytest.fixture
def parsed_content_param_routes(schema_with_content_params)
⋮----
"""Return parsed routes from the schema with content parameters."""
⋮----
@pytest.fixture
def schema_all_http_methods() -> dict[str, Any]
⋮----
"""Fixture that returns a schema with all HTTP methods."""
⋮----
@pytest.fixture
def parsed_http_methods_routes(schema_all_http_methods)
⋮----
"""Return parsed routes from the schema with all HTTP methods."""
⋮----
# --- Tests for complex schemas with references --- #
def test_complex_schema_route_count(parsed_complex_routes)
⋮----
"""Test that parsing a schema with references successfully extracts all routes."""
⋮----
def test_complex_schema_ref_rewriting(parsed_complex_routes)
⋮----
"""Test that all #/components references have been rewritten."""
def no_components(value)
⋮----
def test_complex_schema_list_users_query_param_limit(complex_route_map)
⋮----
"""Test that a reference to a limit query parameter is correctly resolved."""
list_users = complex_route_map["listUsers"]
limit_param = next((p for p in list_users.parameters if p.name == "limit"), None)
⋮----
def test_complex_schema_list_users_query_param_limit_maximum(complex_route_map)
⋮----
"""Test that a limit parameter's maximum value is correctly resolved."""
⋮----
def test_complex_schema_get_user_path_param_existence(complex_route_map)
⋮----
"""Test that a reference to a path parameter exists."""
get_user = complex_route_map["getUser"]
user_id_param = next((p for p in get_user.parameters if p.name == "userId"), None)
⋮----
def test_complex_schema_get_user_path_param_required(complex_route_map)
⋮----
"""Test that a path parameter is correctly marked as required."""
⋮----
def test_complex_schema_get_user_path_param_format(complex_route_map)
⋮----
"""Test that a path parameter format is correctly resolved."""
⋮----
def test_complex_schema_create_order_request_body_presence(complex_route_map)
⋮----
"""Test that a reference to a request body is resolved correctly."""
create_order = complex_route_map["createOrder"]
⋮----
def test_complex_schema_create_order_request_body_content_type(complex_route_map)
⋮----
"""Test that request body content type is correctly resolved."""
⋮----
def test_complex_schema_create_order_request_body_properties(complex_route_map)
⋮----
"""Test that request body properties are correctly resolved."""
⋮----
json_schema = create_order.request_body.content_schema["application/json"]
⋮----
def test_complex_schema_create_order_request_body_required_fields(complex_route_map)
⋮----
"""Test that request body required fields are correctly resolved."""
⋮----
# --- Tests for schema reference resolution errors --- #
def test_parser_handles_broken_references(schema_with_invalid_reference)
⋮----
"""Test that parser handles broken references gracefully."""
# We're just checking that the function doesn't throw an exception
routes = parse_openapi_to_http_routes(schema_with_invalid_reference)
# Should still return routes list (may be empty)
⋮----
# Verify that the route with broken parameter reference is still included
# though it may not have the parameter properly
broken_route = next(
# The route should still be present
⋮----
# --- Tests for content-based parameters --- #
def test_content_param_parameter_name(parsed_content_param_routes)
⋮----
"""Test that parser correctly extracts name for content-based parameters."""
complex_params = parsed_content_param_routes[0]
⋮----
param = complex_params.parameters[0]
⋮----
def test_content_param_parameter_location(parsed_content_param_routes)
⋮----
"""Test that parser correctly extracts location for content-based parameters."""
⋮----
def test_content_param_schema_properties_presence(parsed_content_param_routes)
⋮----
"""Test that parser extracts schema properties from content-based parameter."""
⋮----
properties = param.schema_.get("properties", {})
⋮----
def test_content_param_schema_enum_presence(parsed_content_param_routes)
⋮----
"""Test that parser extracts enum values from content-based parameter."""
⋮----
# --- Tests for HTTP methods --- #
def test_http_get_method_presence(parsed_http_methods_routes)
⋮----
"""Test that GET method is correctly extracted."""
get_route = next((r for r in parsed_http_methods_routes if r.method == "GET"), None)
⋮----
def test_http_get_method_path(parsed_http_methods_routes)
⋮----
"""Test that GET method path is correctly extracted."""
⋮----
def test_http_post_method_presence(parsed_http_methods_routes)
⋮----
"""Test that POST method is correctly extracted."""
post_route = next(
⋮----
def test_http_post_method_path(parsed_http_methods_routes)
⋮----
"""Test that POST method path is correctly extracted."""
⋮----
def test_http_put_method_presence(parsed_http_methods_routes)
⋮----
"""Test that PUT method is correctly extracted."""
put_route = next((r for r in parsed_http_methods_routes if r.method == "PUT"), None)
⋮----
def test_http_put_method_path(parsed_http_methods_routes)
⋮----
"""Test that PUT method path is correctly extracted."""
⋮----
def test_http_delete_method_presence(parsed_http_methods_routes)
⋮----
"""Test that DELETE method is correctly extracted."""
delete_route = next(
⋮----
def test_http_delete_method_path(parsed_http_methods_routes)
⋮----
"""Test that DELETE method path is correctly extracted."""
⋮----
def test_http_patch_method_presence(parsed_http_methods_routes)
⋮----
"""Test that PATCH method is correctly extracted."""
patch_route = next(
⋮----
def test_http_patch_method_path(parsed_http_methods_routes)
⋮----
"""Test that PATCH method path is correctly extracted."""
⋮----
def test_http_head_method_presence(parsed_http_methods_routes)
⋮----
"""Test that HEAD method is correctly extracted."""
head_route = next(
⋮----
def test_http_head_method_path(parsed_http_methods_routes)
⋮----
"""Test that HEAD method path is correctly extracted."""
⋮----
def test_http_options_method_presence(parsed_http_methods_routes)
⋮----
"""Test that OPTIONS method is correctly extracted."""
options_route = next(
⋮----
def test_http_options_method_path(parsed_http_methods_routes)
⋮----
"""Test that OPTIONS method path is correctly extracted."""
⋮----
def test_http_trace_method_presence(parsed_http_methods_routes)
⋮----
"""Test that TRACE method is correctly extracted."""
trace_route = next(
⋮----
def test_http_trace_method_path(parsed_http_methods_routes)
⋮----
"""Test that TRACE method path is correctly extracted."""
⋮----
@pytest.fixture
def schema_with_external_reference() -> dict[str, Any]
⋮----
"""Fixture that returns a schema with external schema references like in issue #926."""
⋮----
# --- Tests for external schema reference handling --- #
def test_external_reference_raises_clear_error(schema_with_external_reference)
⋮----
"""Test that external schema references raise a clear, helpful error message."""
⋮----
error_message = str(exc_info.value)

================
File: tests/utilities/openapi/test_openapi_fastapi.py
================
"""Tests for FastAPI integration with the OpenAPI utilities."""
⋮----
@pytest.fixture
def fastapi_app() -> FastAPI
⋮----
"""Fixture that returns a FastAPI app for live OpenAPI schema testing."""
⋮----
class ItemStatus(str, Enum)
⋮----
available = "available"
pending = "pending"
sold = "sold"
class Tag(BaseModel)
⋮----
id: int
name: str
class Item(BaseModel)
⋮----
"""Example pydantic model for testing OpenAPI schema generation."""
⋮----
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = Field(default_factory=list)
status: ItemStatus = ItemStatus.available
dimensions: dict[str, float] | None = None
# Create a FastAPI app with comprehensive features
app = FastAPI(
⋮----
"""Example dependency function for header validation."""
⋮----
TokenDep = Depends(get_token_header)
⋮----
"""List all items with pagination and optional status filtering."""
fake_items = [
⋮----
fake_items = [item for item in fake_items if item.get("status") == status]
⋮----
"""Create a new item (requires authentication)."""
⋮----
"""Get details about a specific item."""
item = {
⋮----
"""Update an existing item (requires authentication)."""
⋮----
"""Delete an item (requires authentication)."""
⋮----
"""Update just the tags of an item."""
⋮----
"""Get a specific tag for an item."""
⋮----
"""Upload a file (dummy endpoint for testing query params)."""
⋮----
# Add a callback route for testing complex documentation
⋮----
callbacks={  # type: ignore
⋮----
"""Register a webhook for processing notifications."""
⋮----
@pytest.fixture
def fastapi_openapi_schema(fastapi_app) -> dict[str, Any]
⋮----
"""Fixture that returns the OpenAPI schema from a live FastAPI server."""
⋮----
@pytest.fixture
def parsed_routes(fastapi_openapi_schema)
⋮----
"""Return parsed routes from a FastAPI OpenAPI schema."""
⋮----
@pytest.fixture
def route_map(parsed_routes)
⋮----
"""Return a dictionary of routes by operation ID."""
⋮----
def test_parse_fastapi_schema_route_count(parsed_routes)
⋮----
"""Test that all routes are parsed from the FastAPI schema."""
assert len(parsed_routes) == 9  # 8 endpoints + 1 callback
def test_parse_fastapi_schema_operation_ids(route_map)
⋮----
"""Test that all expected operation IDs are present in the parsed schema."""
expected_operations = [
⋮----
def test_path_parameter_parsing(route_map)
⋮----
"""Test that path parameters are correctly parsed."""
get_item = route_map["get_item"]
path_params = [p for p in get_item.parameters if p.location == "path"]
⋮----
def test_query_parameter_parsing(route_map)
⋮----
"""Test that query parameters are correctly parsed."""
list_items = route_map["list_items"]
query_params = [p for p in list_items.parameters if p.location == "query"]
assert len(query_params) == 3  # skip, limit, status
param_names = [p.name for p in query_params]
⋮----
def test_header_parameter_parsing(route_map)
⋮----
"""Test that header parameters from dependencies are correctly parsed."""
create_item = route_map["create_item"]
header_params = [p for p in create_item.parameters if p.location == "header"]
⋮----
def test_request_body_content_type(route_map)
⋮----
"""Test that request body content types are correctly parsed."""
⋮----
def test_request_body_properties(route_map)
⋮----
"""Test that request body properties are correctly parsed."""
⋮----
json_schema = create_item.request_body.content_schema["application/json"]
properties = json_schema.get("properties", {})
⋮----
def test_request_body_status_schema(route_map)
⋮----
"""Test that the status schema in request body is correctly handled."""
⋮----
status_schema = properties.get("status", {})
# FastAPI may represent enums as references or directly include enum values
⋮----
def test_route_with_items_tag(parsed_routes)
⋮----
"""Test that routes with 'items' tag are correctly parsed."""
item_routes = [r for r in parsed_routes if "items" in r.tags]
assert len(item_routes) >= 6  # At least 6 endpoints with "items" tag
def test_routes_with_multiple_tags(parsed_routes)
⋮----
"""Test that routes with multiple tags are correctly parsed."""
multi_tag_routes = [r for r in parsed_routes if len(r.tags) > 1]
assert len(multi_tag_routes) >= 2  # At least 2 endpoints with multiple tags
def test_specific_route_tags(route_map)
⋮----
"""Test that specific routes have the expected tags."""
⋮----
def test_operation_summary(route_map)
⋮----
"""Test that operation summary is correctly parsed."""
⋮----
def test_operation_description(route_map)
⋮----
"""Test that operation description is correctly parsed."""
⋮----
def test_path_with_route_parameters(route_map)
⋮----
"""Test that paths with route parameters are correctly parsed."""
⋮----
def test_complex_nested_paths(route_map)
⋮----
"""Test that complex nested paths are correctly parsed."""
get_item_tag = route_map["get_item_tag"]
⋮----
def test_http_methods(route_map)
⋮----
"""Test that HTTP methods are correctly parsed."""
⋮----
def test_item_schema_properties(route_map)
⋮----
"""Test that Item schema properties are correctly resolved."""
⋮----
def test_webhook_endpoint(route_map)
⋮----
"""Test parsing of webhook registration endpoint."""
webhook = route_map["register_webhook"]
⋮----
def test_webhook_request_body(route_map)
⋮----
"""Test that webhook request body is correctly parsed."""
⋮----
json_schema = webhook.request_body.content_schema["application/json"]
⋮----
def test_token_dependency_handling(route_map)
⋮----
"""Test that token dependencies are correctly handled in parsed endpoints."""
token_endpoints = ["create_item", "update_item", "delete_item"]
⋮----
route = route_map[op_id]
header_params = [p for p in route.parameters if p.location == "header"]
token_headers = [p for p in header_params if p.name == "x-token"]
⋮----
# --- Additional Tag-related Tests --- #
def test_all_routes_have_tags(parsed_routes)
⋮----
"""Test that all routes have a non-empty tags list."""
⋮----
# FastAPI adds tags to all routes in our test fixture
⋮----
def test_tag_consistency_across_related_endpoints(route_map)
⋮----
"""Test that related endpoints have consistent tags."""
# All item endpoints should have the "items" tag
item_endpoints = [
⋮----
# Tag-related endpoints should have both "items" and "tags" tags
tag_endpoints = ["update_item_tags", "get_item_tag"]
⋮----
def test_tag_order_preservation(fastapi_app)
⋮----
"""Test that tag order is preserved in the parsed routes."""
# Add a new endpoint with specifically ordered tags
⋮----
async def test_tag_order()
# Get the updated schema and parse routes
routes = parse_openapi_to_http_routes(fastapi_app.openapi())
# Find our test route
test_route = next((r for r in routes if r.path == "/test-tag-order"), None)
⋮----
# Check tag order is preserved
⋮----
def test_duplicate_tags_handling(fastapi_app)
⋮----
"""Test handling of duplicate tags in the OpenAPI schema."""
# Add an endpoint with duplicate tags
⋮----
async def test_duplicate_tags()
⋮----
test_route = next((r for r in routes if r.path == "/test-duplicate-tags"), None)
⋮----
# Check that duplicate tags are preserved (FastAPI might deduplicate)
# We'll test both possibilities to be safe
⋮----
def test_repr_http_routes(parsed_routes)
⋮----
"""Test that HTTPRoute objects can be represented without recursion errors."""
# Test repr on all parsed routes
⋮----
route_repr = repr(route)
# Verify repr contains essential information
⋮----
# If operation_id exists, it should be in the repr

================
File: tests/utilities/openapi/test_openapi.py
================
"""Tests for the OpenAPI parsing utilities."""
⋮----
# --- Test Data: Static OpenAPI Schema Dictionaries --- #
⋮----
@pytest.fixture
def petstore_schema() -> dict[str, Any]
⋮----
"""Fixture that returns a simple Pet Store API schema."""
⋮----
"parameters": [  # Path level parameter example
⋮----
@pytest.fixture
def parsed_petstore_routes(petstore_schema)
⋮----
"""Return parsed routes from the PetStore schema."""
⋮----
@pytest.fixture
def bookstore_schema() -> dict[str, Any]
⋮----
"""Fixture that returns a Book Store API schema with different parameter types."""
⋮----
@pytest.fixture
def parsed_bookstore_routes(bookstore_schema)
⋮----
"""Return parsed routes from the BookStore schema."""
⋮----
# --- FastAPI App Fixtures --- #
class Item(BaseModel)
⋮----
"""Example pydantic model for API testing."""
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = Field(default_factory=list)
⋮----
@pytest.fixture
def fastapi_app() -> FastAPI
⋮----
"""Fixture that returns a FastAPI app with various types of endpoints."""
app = FastAPI(title="Test API", version="1.0.0")
⋮----
@app.get("/items/", operation_id="list_items")
    async def list_items(skip: int = 0, limit: int = 10)
⋮----
"""List all items with pagination."""
⋮----
@app.post("/items/", operation_id="create_item")
    async def create_item(item: Item)
⋮----
"""Create a new item."""
⋮----
"""Get an item by ID."""
⋮----
"""Update an existing item."""
⋮----
"""Delete an item by ID."""
⋮----
"""Get a specific tag for an item."""
⋮----
"""Upload a file (dummy endpoint for testing query params with POST)."""
⋮----
@pytest.fixture
def fastapi_openapi_schema(fastapi_app) -> dict[str, Any]
⋮----
"""Fixture that returns the OpenAPI schema of the FastAPI app."""
⋮----
@pytest.fixture
def parsed_fastapi_routes(fastapi_openapi_schema)
⋮----
"""Return parsed routes from a FastAPI OpenAPI schema."""
⋮----
@pytest.fixture
def fastapi_route_map(parsed_fastapi_routes)
⋮----
"""Return a dictionary of routes by operation ID."""
⋮----
@pytest.fixture
def openapi_30_schema() -> dict[str, Any]
⋮----
"""Fixture that returns a simple OpenAPI 3.0.0 schema."""
⋮----
@pytest.fixture
def openapi_31_schema() -> dict[str, Any]
⋮----
"""Fixture that returns a simple OpenAPI 3.1.0 schema."""
⋮----
@pytest.fixture
def openapi_30_with_references() -> dict[str, Any]
⋮----
"""OpenAPI 3.0 schema with references to test resolution."""
⋮----
@pytest.fixture
def openapi_31_with_references() -> dict[str, Any]
⋮----
"""OpenAPI 3.1 schema with references to test resolution."""
⋮----
# --- Tests for PetStore schema --- #
def test_petstore_route_count(parsed_petstore_routes)
⋮----
"""Test that parsing the PetStore schema correctly identifies the number of routes."""
⋮----
def test_petstore_get_pets_operation_id(parsed_petstore_routes)
⋮----
"""Test that GET /pets operation_id is correctly parsed."""
get_pets = next(
⋮----
def test_petstore_query_parameter(parsed_petstore_routes)
⋮----
"""Test that query parameter 'limit' is correctly parsed from the schema."""
⋮----
param = get_pets.parameters[0]
⋮----
def test_petstore_path_parameter(parsed_petstore_routes)
⋮----
"""Test that path parameter 'petId' is correctly parsed from the schema."""
get_pet = next(
⋮----
path_param = next((p for p in get_pet.parameters if p.name == "petId"), None)
⋮----
def test_petstore_header_parameters(parsed_petstore_routes)
⋮----
"""Test that header parameters are correctly parsed from the schema."""
⋮----
header_params = [p for p in get_pet.parameters if p.location == "header"]
⋮----
def test_petstore_header_parameter_names(parsed_petstore_routes)
⋮----
"""Test that header parameter names are correctly parsed."""
⋮----
header_names = [p.name for p in header_params]
⋮----
def test_petstore_path_level_parameters(parsed_petstore_routes)
⋮----
"""Test that path-level parameters are correctly merged into the operation."""
⋮----
trace_param = next((p for p in get_pet.parameters if p.name == "traceId"), None)
⋮----
def test_petstore_request_body_reference_resolution(parsed_petstore_routes)
⋮----
"""Test that request body references are correctly resolved."""
create_pet = next(
⋮----
def test_petstore_schema_reference_resolution(parsed_petstore_routes)
⋮----
"""Test that schema references in request bodies are correctly resolved."""
⋮----
json_schema = create_pet.request_body.content_schema["application/json"]
properties = json_schema.get("properties", {})
⋮----
def test_petstore_required_fields_resolution(parsed_petstore_routes)
⋮----
"""Test that required fields are correctly resolved from referenced schemas."""
⋮----
def test_tags_parsing_in_petstore_routes(parsed_petstore_routes)
⋮----
"""Test that tags are correctly parsed from the OpenAPI schema."""
# All petstore routes should have the "pets" tag
⋮----
def test_tag_list_structure(parsed_petstore_routes)
⋮----
"""Test that tags are stored as a list of strings."""
⋮----
def test_empty_tags_handling(bookstore_schema)
⋮----
"""Test that routes with no tags are handled correctly with empty lists."""
# Modify a route to remove tags
⋮----
# Parse the modified schema
routes = parse_openapi_to_http_routes(bookstore_schema)
# Find the GET /books route
get_books = next(
⋮----
# Should have an empty list, not None
⋮----
def test_multiple_tags_preserved(bookstore_schema)
⋮----
"""Test that multiple tags are preserved during parsing."""
# Add multiple tags to a route
⋮----
# Should have all tags
⋮----
def test_openapi_extensions(petstore_schema)
⋮----
"""Test that OpenAPI extensions (x-*) are correctly parsed from operations."""
# Add extensions to a route
⋮----
routes = parse_openapi_to_http_routes(petstore_schema)
# Find the GET /pets route
⋮----
# Should have extensions
⋮----
# --- Tests for BookStore schema --- #
def test_bookstore_route_count(parsed_bookstore_routes)
⋮----
"""Test that parsing the BookStore schema correctly identifies the number of routes."""
⋮----
def test_bookstore_query_parameter_count(parsed_bookstore_routes)
⋮----
"""Test that the correct number of query parameters are parsed."""
list_books = next(
⋮----
def test_bookstore_query_parameter_names(parsed_bookstore_routes)
⋮----
"""Test that query parameter names are correctly parsed."""
⋮----
param_map = {p.name: p for p in list_books.parameters}
⋮----
def test_bookstore_query_parameter_formats(parsed_bookstore_routes)
⋮----
"""Test that query parameter formats are correctly parsed."""
⋮----
def test_bookstore_query_parameter_defaults(parsed_bookstore_routes)
⋮----
"""Test that query parameter default values are correctly parsed."""
⋮----
def test_bookstore_inline_request_body_presence(parsed_bookstore_routes)
⋮----
"""Test that request bodies with inline schemas are present."""
create_book = next(
⋮----
def test_bookstore_inline_request_body_properties(parsed_bookstore_routes)
⋮----
"""Test that request body properties are correctly parsed from inline schemas."""
⋮----
json_schema = create_book.request_body.content_schema["application/json"]
⋮----
def test_bookstore_inline_request_body_required_fields(parsed_bookstore_routes)
⋮----
"""Test that required fields in inline schema are correctly parsed."""
⋮----
def test_bookstore_delete_method(parsed_bookstore_routes)
⋮----
"""Test that DELETE method is correctly parsed from the schema."""
delete_book = next(
⋮----
def test_bookstore_delete_method_parameters(parsed_bookstore_routes)
⋮----
"""Test that parameters for DELETE method are correctly parsed."""
⋮----
# --- Tests for FastAPI Generated Schema --- #
def test_fastapi_route_count(parsed_fastapi_routes)
⋮----
"""Test that parsing a FastAPI-generated schema correctly identifies the number of routes."""
⋮----
def test_fastapi_parameter_default_values(fastapi_route_map)
⋮----
"""Test that default parameter values are correctly parsed from the schema."""
list_items = fastapi_route_map["list_items"]
param_map = {p.name: p for p in list_items.parameters}
⋮----
def test_fastapi_skip_parameter_default(fastapi_route_map)
⋮----
"""Test that skip parameter default value is correctly parsed."""
⋮----
def test_fastapi_limit_parameter_default(fastapi_route_map)
⋮----
"""Test that limit parameter default value is correctly parsed."""
⋮----
def test_fastapi_request_body_from_pydantic(fastapi_route_map)
⋮----
"""Test that request bodies from Pydantic models are present."""
create_item = fastapi_route_map["create_item"]
⋮----
def test_fastapi_request_body_properties(fastapi_route_map)
⋮----
"""Test that request body properties from Pydantic models are correctly parsed."""
⋮----
json_schema = create_item.request_body.content_schema["application/json"]
⋮----
def test_fastapi_request_body_required_fields(fastapi_route_map)
⋮----
"""Test that required fields from Pydantic models are correctly parsed."""
⋮----
required = json_schema.get("required", [])
⋮----
def test_fastapi_path_parameter_presence(fastapi_route_map)
⋮----
"""Test that path parameters are present in FastAPI schema."""
get_item = fastapi_route_map["get_item"]
path_params = [p for p in get_item.parameters if p.location == "path"]
⋮----
def test_fastapi_path_parameter_properties(fastapi_route_map)
⋮----
"""Test that path parameters properties are correctly parsed."""
⋮----
def test_fastapi_optional_query_parameter(fastapi_route_map)
⋮----
"""Test that optional query parameters are correctly parsed."""
⋮----
query_params = [p for p in get_item.parameters if p.location == "query"]
⋮----
def test_fastapi_multiple_path_parameter_count(fastapi_route_map)
⋮----
"""Test that multiple path parameters count is correct."""
get_item_tag = fastapi_route_map["get_item_tag"]
path_params = [p for p in get_item_tag.parameters if p.location == "path"]
⋮----
def test_fastapi_multiple_path_parameter_names(fastapi_route_map)
⋮----
"""Test that multiple path parameter names are correctly parsed."""
⋮----
param_names = [p.name for p in path_params]
⋮----
def test_fastapi_post_with_query_parameters(fastapi_route_map)
⋮----
"""Test that query parameters for POST methods are correctly parsed."""
upload_file = fastapi_route_map["upload_file"]
⋮----
query_params = [p for p in upload_file.parameters if p.location == "query"]
⋮----
def test_fastapi_post_query_parameter_names(fastapi_route_map)
⋮----
"""Test that query parameter names for POST methods are correctly parsed."""
⋮----
param_names = [p.name for p in query_params]
⋮----
def test_openapi_30_compatibility(openapi_30_schema)
⋮----
"""Test that OpenAPI 3.0 schemas can be parsed correctly."""
# This will raise an exception if the parser doesn't support 3.0.0
routes = parse_openapi_to_http_routes(openapi_30_schema)
# Verify the route was parsed correctly
⋮----
route = routes[0]
⋮----
def test_openapi_31_compatibility(openapi_31_schema)
⋮----
"""Test that OpenAPI 3.1 schemas can be parsed correctly."""
routes = parse_openapi_to_http_routes(openapi_31_schema)
⋮----
def test_version_detection_logic()
⋮----
"""Test that the version detection logic correctly identifies 3.0 vs 3.1 schemas."""
# Test 3.0 variations
⋮----
schema = {
⋮----
# Expect no error
⋮----
# Test 3.1 variations
⋮----
def test_openapi_30_reference_resolution(openapi_30_with_references)
⋮----
"""Test that references are correctly resolved in OpenAPI 3.0 schemas."""
routes = parse_openapi_to_http_routes(openapi_30_with_references)
⋮----
# Check request body
⋮----
# Check schema structure
json_schema = route.request_body.content_schema["application/json"]
⋮----
# Check primary fields are properly resolved
props = json_schema["properties"]
⋮----
# The category might be a reference or resolved object
category = props["category"]
# Either it's directly resolved with properties
# or it still has a $ref field
⋮----
combined_schema = _combine_schemas(route)
⋮----
def test_openapi_31_reference_resolution(openapi_31_with_references)
⋮----
"""Test that references are correctly resolved in OpenAPI 3.1 schemas."""
routes = parse_openapi_to_http_routes(openapi_31_with_references)
⋮----
"""Test that both parsers produce equivalent output for equivalent schemas."""
routes_30 = parse_openapi_to_http_routes(openapi_30_with_references)
routes_31 = parse_openapi_to_http_routes(openapi_31_with_references)
# Convert to dict for easier comparison
route_30_dict = routes_30[0].model_dump(exclude_none=True)
route_31_dict = routes_31[0].model_dump(exclude_none=True)
# They should be identical except for version-specific differences
# Compare path
⋮----
# Compare method
⋮----
# Compare operation_id
⋮----
# Compare parameters
⋮----
# Compare request body
⋮----
# Compare response structure
⋮----
# The schemas should contain the same essential fields
schema_30 = route_30_dict["request_body"]["content_schema"]["application/json"][
schema_31 = route_31_dict["request_body"]["content_schema"]["application/json"][
⋮----
class TestReplaceRefWithDefs
⋮----
@pytest.fixture(scope="class")
    def schemas(self)
⋮----
"""Provide test schemas for _replace_ref_with_defs function."""
⋮----
def test_replace_direct_ref(self, schemas)
⋮----
"""Test replacing direct $ref references."""
result = _replace_ref_with_defs(schemas["ref_type"])
⋮----
def test_replace_object_property_ref(self, schemas)
⋮----
"""Test replacing $ref in object properties."""
result = _replace_ref_with_defs(schemas["object_type"])
⋮----
def test_replace_array_items_ref(self, schemas)
⋮----
"""Test replacing $ref in array items."""
result = _replace_ref_with_defs(schemas["array_type"])
⋮----
def test_replace_any_of_refs(self, schemas)
⋮----
"""Test replacing $ref in anyOf schemas."""
result = _replace_ref_with_defs(schemas["any_of_type"])
⋮----
def test_replace_all_of_refs(self, schemas)
⋮----
"""Test replacing $ref in allOf schemas."""
result = _replace_ref_with_defs(schemas["all_of_type"])
⋮----
def test_replace_one_of_refs(self, schemas)
⋮----
"""Test replacing $ref in oneOf schemas."""
result = _replace_ref_with_defs(schemas["one_of_type"])
⋮----
def test_replace_nested_refs(self, schemas)
⋮----
"""Test replacing $ref in deeply nested schema structures."""
result = _replace_ref_with_defs(schemas["nested_type"])

================
File: tests/utilities/__init__.py
================
"""Tests for utilities in the fastmcp package."""

================
File: tests/utilities/test_cache.py
================
"""Tests for the cache.py module."""
⋮----
class TestTimedCache
⋮----
"""Tests for the TimedCache class."""
def test_init(self)
⋮----
"""Test that a TimedCache can be initialized with an expiration."""
expiration = datetime.timedelta(seconds=10)
cache = TimedCache(expiration)
⋮----
def test_set(self)
⋮----
"""Test that values can be set in the cache."""
cache = TimedCache(datetime.timedelta(seconds=10))
⋮----
now = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
⋮----
# Check that the value is stored with the correct expiration
⋮----
def test_get_found(self)
⋮----
"""Test retrieving a value that exists and has not expired."""
⋮----
# Set a future expiration time
future = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
⋮----
# The value should be returned
⋮----
def test_get_expired(self)
⋮----
"""Test retrieving a value that exists but has expired."""
⋮----
# Set a past expiration time
past = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
⋮----
# Should return NOT_FOUND
⋮----
def test_get_not_found(self)
⋮----
"""Test retrieving a value that doesn't exist in the cache."""
⋮----
# Key doesn't exist
⋮----
def test_clear(self)
⋮----
"""Test that the cache can be cleared."""
⋮----
# Add some items
⋮----
# Clear the cache
⋮----
def test_real_expiration(self)
⋮----
"""Test that values actually expire after the specified time."""
# Use a very short expiration for the test
cache = TimedCache(datetime.timedelta(milliseconds=50))
⋮----
# Value should be available immediately
⋮----
# Wait for expiration
time.sleep(0.06)  # 60 milliseconds, slightly longer than expiration
# Value should now be expired
⋮----
def test_overwrite_value(self)
⋮----
"""Test that setting a key that already exists overwrites the old value."""
⋮----
key = "test_key"
# Set initial value
⋮----
# Overwrite with new value
⋮----
def test_extends_expiration_on_overwrite(self)
⋮----
"""Test that overwriting a key extends its expiration time."""
⋮----
# Set initial value at t=0
initial_time = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
⋮----
initial_expiration = cache.cache[key][1]
⋮----
# Overwrite at t=5
later_time = initial_time + datetime.timedelta(seconds=5)
⋮----
# Expiration should be extended
new_expiration = cache.cache[key][1]
⋮----
def test_different_key_types(self)
⋮----
"""Test that different types of keys can be used."""
⋮----
# Test various key types
keys_and_values = [
⋮----
def test_none_value(self)
⋮----
"""Test that None can be stored as a value."""
⋮----
key = "none_key"
⋮----
# The stored value is None, but get() should return None, not NOT_FOUND
⋮----
def test_edge_case_zero_expiration(self)
⋮----
"""Test with a zero expiration time."""
cache = TimedCache(datetime.timedelta(seconds=0))
⋮----
# The value might already be expired by the time we call get()
# We can't make strong assertions here due to timing variability
retrieved = cache.get(key)
⋮----
def test_negative_expiration(self)
⋮----
"""Test with a negative expiration time."""
cache = TimedCache(datetime.timedelta(seconds=-1))
⋮----
# Value should be immediately expired
⋮----
def test_cache_consistency(self)
⋮----
"""Test cache consistency with multiple operations."""
⋮----
# Add multiple items
⋮----
# Check all items
⋮----
# Overwrite one item
⋮----
# Check again
⋮----
# Clear and verify all items are gone
⋮----
def test_large_expiration(self)
⋮----
"""Test with a very large expiration time."""
# One year expiration
cache = TimedCache(datetime.timedelta(days=365))
⋮----
def test_many_items(self)
⋮----
"""Test cache with many items."""
⋮----
# Add 1000 items
⋮----
# Check size
⋮----
# Check some random items

================
File: tests/utilities/test_inspect.py
================
"""Tests for the inspect.py module."""
# Import FastMCP1x for testing (always available since mcp is a dependency)
⋮----
class TestFastMCPInfo
⋮----
"""Tests for the FastMCPInfo dataclass."""
def test_fastmcp_info_creation(self)
⋮----
"""Test that FastMCPInfo can be created with all required fields."""
tool = ToolInfo(
info = FastMCPInfo(
⋮----
def test_fastmcp_info_with_none_instructions(self)
⋮----
"""Test that FastMCPInfo works with None instructions."""
⋮----
class TestGetFastMCPInfo
⋮----
"""Tests for the get_fastmcp_info function."""
async def test_empty_server(self)
⋮----
"""Test get_fastmcp_info with an empty server."""
mcp = FastMCP("EmptyServer", instructions="Empty server for testing")
info = await inspect_fastmcp(mcp)
⋮----
assert info.server_version == fastmcp.__version__  # v2.x uses FastMCP version
⋮----
async def test_server_with_tools(self)
⋮----
"""Test get_fastmcp_info with a server that has tools."""
mcp = FastMCP("ToolServer")
⋮----
@mcp.tool
        def add_numbers(a: int, b: int) -> int
⋮----
@mcp.tool
        def greet(name: str) -> str
⋮----
tool_names = [tool.name for tool in info.tools]
⋮----
async def test_server_with_resources(self)
⋮----
"""Test get_fastmcp_info with a server that has resources."""
mcp = FastMCP("ResourceServer")
⋮----
@mcp.resource("resource://static")
        def get_static_data() -> str
⋮----
@mcp.resource("resource://dynamic/{param}")
        def get_dynamic_data(param: str) -> str
⋮----
assert len(info.resources) == 1  # Static resource
assert len(info.templates) == 1  # Dynamic resource becomes template
resource_uris = [res.uri for res in info.resources]
template_uris = [tmpl.uri_template for tmpl in info.templates]
⋮----
async def test_server_with_prompts(self)
⋮----
"""Test get_fastmcp_info with a server that has prompts."""
mcp = FastMCP("PromptServer")
⋮----
@mcp.prompt
        def analyze_data(data: str) -> list
⋮----
@mcp.prompt("custom_prompt")
        def custom_analysis(text: str) -> list
⋮----
prompt_names = [prompt.name for prompt in info.prompts]
⋮----
async def test_comprehensive_server(self)
⋮----
"""Test get_fastmcp_info with a server that has all component types."""
mcp = FastMCP("ComprehensiveServer", instructions="A server with everything")
# Add a tool
⋮----
@mcp.tool
        def calculate(x: int, y: int) -> int
# Add a resource
⋮----
@mcp.resource("resource://data")
        def get_data() -> str
# Add a template
⋮----
@mcp.resource("resource://item/{id}")
        def get_item(id: str) -> str
# Add a prompt
⋮----
@mcp.prompt
        def analyze(content: str) -> list
⋮----
# Check all components are present
⋮----
# Check capabilities
⋮----
async def test_server_no_instructions(self)
⋮----
"""Test get_fastmcp_info with a server that has no instructions."""
mcp = FastMCP("NoInstructionsServer")
⋮----
async def test_server_with_client_integration(self)
⋮----
"""Test that the extracted info matches what a client would see."""
mcp = FastMCP("IntegrationServer")
⋮----
@mcp.tool
        def test_tool() -> str
⋮----
@mcp.resource("resource://test")
        def test_resource() -> str
⋮----
@mcp.prompt
        def test_prompt() -> list
# Get info using our function
⋮----
# Verify using client
⋮----
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
⋮----
class TestFastMCP1xCompatibility
⋮----
"""Tests for FastMCP 1.x compatibility."""
async def test_fastmcp1x_detection(self)
⋮----
"""Test that FastMCP1x instances are correctly detected."""
mcp1x = FastMCP1x("Test1x")
mcp2x = FastMCP("Test2x")
⋮----
async def test_fastmcp1x_empty_server(self)
⋮----
"""Test get_fastmcp_info_v1 with an empty FastMCP1x server."""
mcp = FastMCP1x("Test1x")
info = await inspect_fastmcp_v1(mcp)
⋮----
assert info.server_version == "1.0"  # v1.x servers use "1.0"
⋮----
assert info.templates == []  # No templates added in this test
⋮----
async def test_fastmcp1x_with_tools(self)
⋮----
"""Test get_fastmcp_info_v1 with a FastMCP1x server that has tools."""
⋮----
@mcp.tool()
        def add_numbers(a: int, b: int) -> int
⋮----
@mcp.tool()
        def greet(name: str) -> str
⋮----
async def test_fastmcp1x_with_resources(self)
⋮----
"""Test get_fastmcp_info_v1 with a FastMCP1x server that has resources."""
⋮----
assert len(info.templates) == 0  # No templates added in this test
async def test_fastmcp1x_with_prompts(self)
⋮----
"""Test get_fastmcp_info_v1 with a FastMCP1x server that has prompts."""
⋮----
@mcp.prompt("analyze")
        def analyze_data(data: str) -> list
⋮----
async def test_dispatcher_with_fastmcp1x(self)
⋮----
"""Test that the main get_fastmcp_info function correctly dispatches to v1."""
⋮----
@mcp.tool()
        def test_tool() -> str
⋮----
async def test_dispatcher_with_fastmcp2x(self)
⋮----
"""Test that the main get_fastmcp_info function correctly dispatches to v2."""
mcp = FastMCP("Test2x")
⋮----
async def test_fastmcp1x_vs_fastmcp2x_comparison(self)
⋮----
"""Test that both versions can be inspected and compared."""
⋮----
@mcp1x.tool()
        def tool1x() -> str
⋮----
@mcp2x.tool
        def tool2x() -> str
info1x = await inspect_fastmcp(mcp1x)
info2x = await inspect_fastmcp(mcp2x)
⋮----
tool1x_names = [tool.name for tool in info1x.tools]
tool2x_names = [tool.name for tool in info2x.tools]
⋮----
# Check server versions
⋮----
# No templates added in these tests

================
File: tests/utilities/test_json_schema_type.py
================
class TestSimpleTypes
⋮----
"""Test suite for basic type validation."""
⋮----
@pytest.fixture
    def simple_string(self)
⋮----
@pytest.fixture
    def simple_number(self)
⋮----
@pytest.fixture
    def simple_integer(self)
⋮----
@pytest.fixture
    def simple_boolean(self)
⋮----
@pytest.fixture
    def simple_null(self)
def test_string_accepts_string(self, simple_string)
⋮----
validator = TypeAdapter(simple_string)
⋮----
def test_string_rejects_number(self, simple_string)
def test_number_accepts_float(self, simple_number)
⋮----
validator = TypeAdapter(simple_number)
⋮----
def test_number_accepts_integer(self, simple_number)
def test_number_accepts_numeric_string(self, simple_number)
def test_number_rejects_invalid_string(self, simple_number)
def test_integer_accepts_integer(self, simple_integer)
⋮----
validator = TypeAdapter(simple_integer)
⋮----
def test_integer_accepts_integer_string(self, simple_integer)
def test_integer_rejects_float(self, simple_integer)
def test_integer_rejects_float_string(self, simple_integer)
def test_boolean_accepts_boolean(self, simple_boolean)
⋮----
validator = TypeAdapter(simple_boolean)
⋮----
def test_boolean_accepts_boolean_strings(self, simple_boolean)
def test_boolean_rejects_invalid_string(self, simple_boolean)
def test_null_accepts_none(self, simple_null)
⋮----
validator = TypeAdapter(simple_null)
⋮----
def test_null_rejects_false(self, simple_null)
class TestConstrainedTypes
⋮----
def test_constant(self)
⋮----
validator = TypeAdapter(Literal["x"])
schema = validator.json_schema()
type_ = json_schema_to_type(schema)
⋮----
def test_union_constants(self)
⋮----
validator = TypeAdapter(Literal["x"] | Literal["y"])
⋮----
def test_enum_str(self)
⋮----
class MyEnum(Enum)
⋮----
X = "x"
Y = "y"
validator = TypeAdapter(MyEnum)
⋮----
def test_enum_int(self)
⋮----
X = 1
Y = 2
⋮----
def test_choice(self)
⋮----
validator = TypeAdapter(Literal["x", "y"])
⋮----
class TestStringConstraints
⋮----
"""Test suite for string constraint validation."""
⋮----
@pytest.fixture
    def min_length_string(self)
⋮----
@pytest.fixture
    def max_length_string(self)
⋮----
@pytest.fixture
    def pattern_string(self)
⋮----
@pytest.fixture
    def email_string(self)
def test_min_length_accepts_valid(self, min_length_string)
⋮----
validator = TypeAdapter(min_length_string)
⋮----
def test_min_length_rejects_short(self, min_length_string)
def test_max_length_accepts_valid(self, max_length_string)
⋮----
validator = TypeAdapter(max_length_string)
⋮----
def test_max_length_rejects_long(self, max_length_string)
def test_pattern_accepts_valid(self, pattern_string)
⋮----
validator = TypeAdapter(pattern_string)
⋮----
def test_pattern_rejects_invalid(self, pattern_string)
def test_email_accepts_valid(self, email_string)
⋮----
validator = TypeAdapter(email_string)
result = validator.validate_python("test@example.com")
⋮----
def test_email_rejects_invalid(self, email_string)
class TestNumberConstraints
⋮----
"""Test suite for numeric constraint validation."""
⋮----
@pytest.fixture
    def multiple_of_number(self)
⋮----
@pytest.fixture
    def min_number(self)
⋮----
@pytest.fixture
    def exclusive_min_number(self)
⋮----
@pytest.fixture
    def max_number(self)
⋮----
@pytest.fixture
    def exclusive_max_number(self)
def test_multiple_of_accepts_valid(self, multiple_of_number)
⋮----
validator = TypeAdapter(multiple_of_number)
⋮----
def test_multiple_of_rejects_invalid(self, multiple_of_number)
def test_minimum_accepts_equal(self, min_number)
⋮----
validator = TypeAdapter(min_number)
⋮----
def test_minimum_rejects_less(self, min_number)
def test_exclusive_minimum_rejects_equal(self, exclusive_min_number)
⋮----
validator = TypeAdapter(exclusive_min_number)
⋮----
def test_maximum_accepts_equal(self, max_number)
⋮----
validator = TypeAdapter(max_number)
⋮----
def test_maximum_rejects_greater(self, max_number)
def test_exclusive_maximum_rejects_equal(self, exclusive_max_number)
⋮----
validator = TypeAdapter(exclusive_max_number)
⋮----
class TestArrayTypes
⋮----
"""Test suite for array validation."""
⋮----
@pytest.fixture
    def string_array(self)
⋮----
@pytest.fixture
    def min_items_array(self)
⋮----
@pytest.fixture
    def max_items_array(self)
⋮----
@pytest.fixture
    def unique_items_array(self)
def test_array_accepts_valid_items(self, string_array)
⋮----
validator = TypeAdapter(string_array)
⋮----
def test_array_rejects_invalid_items(self, string_array)
def test_min_items_accepts_valid(self, min_items_array)
⋮----
validator = TypeAdapter(min_items_array)
⋮----
def test_min_items_rejects_too_few(self, min_items_array)
def test_max_items_accepts_valid(self, max_items_array)
⋮----
validator = TypeAdapter(max_items_array)
⋮----
def test_max_items_rejects_too_many(self, max_items_array)
def test_unique_items_accepts_unique(self, unique_items_array)
⋮----
validator = TypeAdapter(unique_items_array)
⋮----
def test_unique_items_converts_duplicates(self, unique_items_array)
⋮----
result = validator.validate_python(["a", "a", "b"])
⋮----
class TestObjectTypes
⋮----
"""Test suite for object validation."""
⋮----
@pytest.fixture
    def simple_object(self)
⋮----
@pytest.fixture
    def required_object(self)
⋮----
@pytest.fixture
    def nested_object(self)
⋮----
# Plain dict becomes dict[str, Any] (JSON Schema accurate)
⋮----
# dict[str, Any] stays the same
⋮----
# Simple typed dicts work correctly
⋮----
# Union value types work
⋮----
# Key types are constrained to str in JSON Schema
⋮----
# Union key types become str (JSON Schema limitation)
⋮----
def test_dict_types_are_generated_correctly(self, input_type, expected_type)
⋮----
schema = TypeAdapter(input_type).json_schema()
generated_type = json_schema_to_type(schema)
⋮----
def test_object_accepts_valid(self, simple_object)
⋮----
validator = TypeAdapter(simple_object)
result = validator.validate_python({"name": "test", "age": 30})
⋮----
def test_object_accepts_extra_properties(self, simple_object)
⋮----
result = validator.validate_python(
⋮----
def test_required_accepts_valid(self, required_object)
⋮----
validator = TypeAdapter(required_object)
result = validator.validate_python({"name": "test"})
⋮----
def test_required_rejects_missing(self, required_object)
def test_nested_accepts_valid(self, nested_object)
⋮----
validator = TypeAdapter(nested_object)
result = validator.validate_python({"user": {"name": "test", "age": 30}})
⋮----
def test_nested_rejects_invalid(self, nested_object)
def test_object_with_underscore_names(self)
⋮----
@dataclass
        class Data
⋮----
x: int
x_: int
_x: int
schema = TypeAdapter(Data).json_schema()
⋮----
object = json_schema_to_type(schema)
object_schema = TypeAdapter(object).json_schema()
⋮----
class TestDefaultValues
⋮----
"""Test suite for default value handling."""
⋮----
@pytest.fixture
    def simple_defaults(self)
⋮----
@pytest.fixture
    def nested_defaults(self)
def test_simple_defaults_empty_object(self, simple_defaults)
⋮----
validator = TypeAdapter(simple_defaults)
result = validator.validate_python({})
⋮----
def test_simple_defaults_partial_override(self, simple_defaults)
def test_nested_defaults_empty_object(self, nested_defaults)
⋮----
validator = TypeAdapter(nested_defaults)
⋮----
def test_nested_defaults_partial_override(self, nested_defaults)
⋮----
result = validator.validate_python({"user": {"name": "test"}})
⋮----
class TestUnionTypes
⋮----
"""Test suite for testing union type behaviors."""
⋮----
@pytest.fixture
    def heterogeneous_union(self)
⋮----
@pytest.fixture
    def union_with_constraints(self)
⋮----
@pytest.fixture
    def union_with_formats(self)
⋮----
@pytest.fixture
    def nested_union_array(self)
⋮----
@pytest.fixture
    def nested_union_object(self)
def test_heterogeneous_accepts_string(self, heterogeneous_union)
⋮----
validator = TypeAdapter(heterogeneous_union)
⋮----
def test_heterogeneous_accepts_number(self, heterogeneous_union)
def test_heterogeneous_accepts_boolean(self, heterogeneous_union)
def test_heterogeneous_accepts_null(self, heterogeneous_union)
def test_heterogeneous_rejects_array(self, heterogeneous_union)
def test_constrained_string_valid(self, union_with_constraints)
⋮----
validator = TypeAdapter(union_with_constraints)
⋮----
def test_constrained_string_invalid(self, union_with_constraints)
def test_constrained_number_valid(self, union_with_constraints)
def test_constrained_number_invalid(self, union_with_constraints)
def test_format_valid_email(self, union_with_formats)
⋮----
validator = TypeAdapter(union_with_formats)
⋮----
def test_format_valid_null(self, union_with_formats)
def test_format_invalid_email(self, union_with_formats)
def test_nested_array_mixed_types(self, nested_union_array)
⋮----
validator = TypeAdapter(nested_union_array)
result = validator.validate_python(["test", 123, "abc"])
⋮----
def test_nested_array_rejects_invalid(self, nested_union_array)
def test_nested_object_string_id(self, nested_union_object)
⋮----
validator = TypeAdapter(nested_union_object)
result = validator.validate_python({"id": "abc123", "data": {"value": "test"}})
⋮----
def test_nested_object_integer_id(self, nested_union_object)
⋮----
result = validator.validate_python({"id": 123, "data": None})
⋮----
class TestFormatTypes
⋮----
"""Test suite for format type validation."""
⋮----
@pytest.fixture
    def datetime_format(self)
⋮----
@pytest.fixture
    def email_format(self)
⋮----
@pytest.fixture
    def uri_format(self)
⋮----
@pytest.fixture
    def uri_reference_format(self)
⋮----
@pytest.fixture
    def json_format(self)
⋮----
@pytest.fixture
    def mixed_formats_object(self)
def test_datetime_valid(self, datetime_format)
⋮----
validator = TypeAdapter(datetime_format)
result = validator.validate_python("2024-01-17T12:34:56Z")
⋮----
def test_datetime_invalid(self, datetime_format)
def test_email_valid(self, email_format)
⋮----
validator = TypeAdapter(email_format)
⋮----
def test_email_invalid(self, email_format)
def test_uri_valid(self, uri_format)
⋮----
validator = TypeAdapter(uri_format)
result = validator.validate_python("https://example.com")
⋮----
def test_uri_invalid(self, uri_format)
def test_uri_reference_valid(self, uri_reference_format)
⋮----
validator = TypeAdapter(uri_reference_format)
⋮----
def test_uri_reference_relative_valid(self, uri_reference_format)
⋮----
result = validator.validate_python("/path/to/resource")
⋮----
def test_uri_reference_invalid(self, uri_reference_format)
⋮----
result = validator.validate_python("not a uri")
⋮----
def test_json_valid(self, json_format)
⋮----
validator = TypeAdapter(json_format)
result = validator.validate_python('{"key": "value"}')
⋮----
def test_json_invalid(self, json_format)
def test_mixed_formats_object(self, mixed_formats_object)
⋮----
validator = TypeAdapter(mixed_formats_object)
⋮----
class TestCircularReferences
⋮----
"""Test suite for circular reference handling."""
⋮----
@pytest.fixture
    def self_referential(self)
⋮----
@pytest.fixture
    def mutually_recursive(self)
def test_self_ref_single_level(self, self_referential)
⋮----
validator = TypeAdapter(self_referential)
⋮----
def test_self_ref_multiple_levels(self, self_referential)
def test_mutual_recursion_single_level(self, mutually_recursive)
⋮----
validator = TypeAdapter(mutually_recursive)
⋮----
def test_mutual_recursion_multiple_levels(self, mutually_recursive)
class TestIdentifierNormalization
⋮----
"""Test suite for handling non-standard property names."""
⋮----
@pytest.fixture
    def special_chars(self)
def test_normalizes_special_chars(self, special_chars)
⋮----
validator = TypeAdapter(special_chars)
⋮----
assert result.field_type == "person"  # @type -> field_type
assert result.first_name == "Alice"  # first-name -> first_name
assert result.last_name == "Smith"  # last.name -> last_name
⋮----
)  # 2nd_address -> field_2nd_address
assert result.field_ref == "12345"  # $ref -> field_ref
class TestConstantValues
⋮----
"""Test suite for constant value validation."""
⋮----
@pytest.fixture
    def string_const(self)
⋮----
@pytest.fixture
    def number_const(self)
⋮----
@pytest.fixture
    def boolean_const(self)
⋮----
@pytest.fixture
    def null_const(self)
⋮----
@pytest.fixture
    def object_with_consts(self)
def test_string_const_valid(self, string_const)
⋮----
validator = TypeAdapter(string_const)
⋮----
def test_string_const_invalid(self, string_const)
def test_number_const_valid(self, number_const)
⋮----
validator = TypeAdapter(number_const)
⋮----
def test_number_const_invalid(self, number_const)
def test_boolean_const_valid(self, boolean_const)
⋮----
validator = TypeAdapter(boolean_const)
⋮----
def test_boolean_const_invalid(self, boolean_const)
def test_null_const_valid(self, null_const)
⋮----
validator = TypeAdapter(null_const)
⋮----
def test_null_const_invalid(self, null_const)
def test_object_consts_valid(self, object_with_consts)
⋮----
validator = TypeAdapter(object_with_consts)
⋮----
def test_object_consts_invalid(self, object_with_consts)
⋮----
"version": 2,  # Wrong constant
⋮----
class TestSchemaCaching
⋮----
"""Test suite for schema caching behavior."""
def test_identical_schemas_reuse_class(self)
⋮----
schema = {"type": "object", "properties": {"name": {"type": "string"}}}
class1 = json_schema_to_type(schema)
class2 = json_schema_to_type(schema)
⋮----
def test_different_names_different_classes(self)
⋮----
class1 = json_schema_to_type(schema, name="Class1")
class2 = json_schema_to_type(schema, name="Class2")
⋮----
def test_nested_schema_caching(self)
⋮----
schema = {
⋮----
# Both main classes and their nested classes should be identical
⋮----
class TestSchemaHashing
⋮----
"""Test suite for schema hashing utility."""
def test_deterministic_hash(self)
⋮----
hash1 = _hash_schema(schema)
hash2 = _hash_schema(schema)
⋮----
assert len(hash1) == 64  # SHA-256 hash length
def test_different_schemas_different_hashes(self)
⋮----
schema1 = {"type": "object", "properties": {"name": {"type": "string"}}}
schema2 = {"type": "object", "properties": {"age": {"type": "integer"}}}
⋮----
def test_order_independent_hash(self)
⋮----
schema1 = {"properties": {"name": {"type": "string"}}, "type": "object"}
schema2 = {"type": "object", "properties": {"name": {"type": "string"}}}
⋮----
def test_nested_schema_hash(self)
class TestDefaultMerging
⋮----
"""Test suite for default value merging behavior."""
def test_simple_merge(self)
⋮----
defaults = {"name": "anonymous", "age": 0}
data = {"name": "test"}
result = _merge_defaults(data, {"properties": {}}, defaults)
⋮----
def test_nested_merge(self)
⋮----
defaults = {"user": {"name": "anonymous", "settings": {"theme": "light"}}}
data = {"user": {"name": "test"}}
⋮----
def test_array_merge(self)
⋮----
defaults = {
data = {"items": [{"name": "custom", "done": True}]}
⋮----
def test_empty_data_uses_defaults(self)
⋮----
result = _merge_defaults({}, schema)
⋮----
def test_property_level_defaults(self)
def test_nested_property_defaults(self)
⋮----
result = _merge_defaults({"user": {"settings": {}}}, schema)
⋮----
def test_default_priority(self)
⋮----
# Test priority: data > parent default > object default > property default
result1 = _merge_defaults({}, schema)  # Uses schema default
⋮----
result2 = _merge_defaults({"settings": {}}, schema)  # Uses object default
⋮----
result3 = _merge_defaults(
⋮----
)  # Uses provided data
⋮----
class TestEdgeCases
⋮----
"""Test suite for edge cases and corner scenarios."""
def test_empty_schema(self)
⋮----
schema = {}
result = json_schema_to_type(schema)
⋮----
def test_schema_without_type(self)
⋮----
schema = {"properties": {"name": {"type": "string"}}}
Type = json_schema_to_type(schema)
validator = TypeAdapter(Type)
⋮----
def test_recursive_defaults(self)
def test_mixed_type_array(self)
⋮----
result = validator.validate_python(["test", 123, True])
⋮----
class TestNameHandling
⋮----
"""Test suite for schema name handling."""
def test_name_from_title(self)
def test_explicit_name_overrides_title(self)
⋮----
Type = json_schema_to_type(schema, name="CustomPerson")
⋮----
def test_default_name_without_title(self)
def test_name_only_allowed_for_objects(self)
⋮----
schema = {"type": "string"}
⋮----
def test_nested_object_names(self)
def test_recursive_schema_naming(self)
def test_name_caching_with_different_titles(self)
⋮----
"""Ensure schemas with different titles create different cached classes"""
schema1 = {
schema2 = {
Type1 = json_schema_to_type(schema1)
Type2 = json_schema_to_type(schema2)
⋮----
def test_recursive_schema_with_invalid_python_name(self)
⋮----
"""Test that recursive schemas work with titles that aren't valid Python identifiers"""
⋮----
# The class should get a sanitized name
⋮----
# Create an instance to verify the recursive reference works
⋮----
class TestAdditionalProperties
⋮----
"""Test suite for additionalProperties handling."""
⋮----
@pytest.fixture
    def dict_only_schema(self)
⋮----
"""Schema with no properties but additionalProperties=True -> dict[str, Any]"""
⋮----
@pytest.fixture
    def properties_with_additional(self)
⋮----
"""Schema with properties AND additionalProperties=True -> BaseModel"""
⋮----
@pytest.fixture
    def properties_without_additional(self)
⋮----
"""Schema with properties but no additionalProperties -> dataclass"""
⋮----
@pytest.fixture
    def required_properties_with_additional(self)
⋮----
"""Schema with required properties AND additionalProperties=True -> BaseModel"""
⋮----
def test_dict_only_returns_dict_type(self, dict_only_schema)
⋮----
"""Test that schema with no properties + additionalProperties=True returns dict[str, Any]"""
⋮----
def test_dict_only_accepts_any_data(self, dict_only_schema)
⋮----
"""Test that pure dict accepts arbitrary key-value pairs"""
validator = TypeAdapter(dict_only_schema)
data = {"anything": "works", "numbers": 123, "nested": {"key": "value"}}
result = validator.validate_python(data)
⋮----
"""Test that schema with properties + additionalProperties=True returns BaseModel"""
⋮----
"""Test that BaseModel with extra='allow' accepts additional properties"""
validator = TypeAdapter(properties_with_additional)
data = {
⋮----
# Check standard properties
⋮----
# Check extra properties are preserved with dot access
⋮----
"""Test that BaseModel still validates known fields"""
⋮----
# Should accept valid data
result = validator.validate_python({"name": "Alice", "age": 30, "extra": "ok"})
⋮----
# Should reject invalid types for known fields
⋮----
"""Test that schema with properties but no additionalProperties returns dataclass"""
⋮----
"""Test that dataclass ignores extra properties (current behavior)"""
validator = TypeAdapter(properties_without_additional)
data = {"name": "Alice", "age": 30, "extra": "ignored"}
⋮----
# Check extra property is ignored
⋮----
"""Test BaseModel with required fields and additional properties"""
validator = TypeAdapter(required_properties_with_additional)
# Should accept valid data with required field
result = validator.validate_python({"name": "Alice", "extra": "field"})
⋮----
assert result.age is None  # Optional field
⋮----
# Should reject missing required field
⋮----
def test_nested_additional_properties(self)
⋮----
"""Test nested objects with additionalProperties"""
⋮----
# Check top-level extra field (BaseModel)
⋮----
# Check nested user extra field (BaseModel)
⋮----
# Check nested settings - should be dataclass
⋮----
# Note: When nested in BaseModel with extra='allow', Pydantic may preserve extra fields
# even on dataclass children. The important thing is that settings is still a dataclass.
⋮----
def test_additional_properties_false_vs_missing(self)
⋮----
"""Test difference between additionalProperties: false and missing additionalProperties"""
# Schema with explicit additionalProperties: false
schema_false = {
# Schema with no additionalProperties key
schema_missing = {
Type_false = json_schema_to_type(schema_false)
Type_missing = json_schema_to_type(schema_missing)
# Both should create dataclasses (not BaseModel)
⋮----
def test_additional_properties_with_defaults(self)
⋮----
"""Test additionalProperties with default values"""
⋮----
# Test with extra fields and defaults
result = validator.validate_python({"extra": "field"})
⋮----
def test_additional_properties_type_consistency(self)
⋮----
"""Test that the same schema always returns the same type"""
⋮----
Type1 = json_schema_to_type(schema)
Type2 = json_schema_to_type(schema)
# Should be the same cached class

================
File: tests/utilities/test_json_schema.py
================
# Wrapper for backward compatibility with tests
def _prune_additional_properties(schema)
⋮----
"""Wrapper for _walk_and_prune that only prunes additionalProperties: false."""
⋮----
class TestPruneParam
⋮----
"""Tests for the _prune_param function."""
def test_nonexistent(self)
⋮----
"""Test pruning a parameter that doesn't exist."""
schema = {"properties": {"foo": {"type": "string"}}}
result = _prune_param(schema, "bar")
assert result == schema  # Schema should be unchanged
def test_exists(self)
⋮----
"""Test pruning a parameter that exists."""
schema = {"properties": {"foo": {"type": "string"}, "bar": {"type": "integer"}}}
⋮----
def test_last_property(self)
⋮----
"""Test pruning the only/last parameter, should leave empty properties object."""
⋮----
result = _prune_param(schema, "foo")
⋮----
def test_from_required(self)
⋮----
"""Test pruning a parameter that's in the required list."""
schema = {
⋮----
def test_last_required(self)
⋮----
"""Test pruning the last required parameter, should remove required field."""
⋮----
class TestPruneUnusedDefs
⋮----
"""Tests for the _prune_unused_defs function."""
def test_removes_unreferenced_defs(self)
⋮----
"""Test that unreferenced definitions are removed."""
⋮----
result = _prune_unused_defs(schema)
⋮----
def test_nested_references_kept(self)
⋮----
"""Test that definitions referenced via nesting are kept."""
⋮----
def test_nested_references_removed(self)
⋮----
"""Test that definitions referenced via nesting in unused defs are removed."""
⋮----
def test_array_references_kept(self)
⋮----
"""Test that definitions referenced in array items are kept."""
⋮----
def test_removes_defs_field_when_empty(self)
⋮----
"""Test that $defs field is removed when all definitions are unused."""
⋮----
class TestPruneAdditionalProperties
⋮----
"""Tests for the _prune_additional_properties function."""
def test_removes_when_false(self)
⋮----
"""Test that additionalProperties is removed when it's false."""
⋮----
result = _prune_additional_properties(schema)
⋮----
def test_keeps_when_true(self)
⋮----
"""Test that additionalProperties is kept when it's true."""
⋮----
def test_keeps_when_object(self)
⋮----
"""Test that additionalProperties is kept when it's an object schema."""
⋮----
class TestCompressSchema
⋮----
"""Tests for the compress_schema function."""
def test_prune_params(self)
⋮----
"""Test pruning parameters with compress_schema."""
⋮----
result = compress_schema(schema, prune_params=["foo", "baz"])
⋮----
def test_prune_defs(self)
⋮----
"""Test pruning unused definitions with compress_schema."""
⋮----
result = compress_schema(schema)
⋮----
def test_disable_prune_defs(self)
⋮----
"""Test disabling pruning of unused definitions."""
⋮----
result = compress_schema(schema, prune_defs=False)
⋮----
def test_pruning_additional_properties(self)
⋮----
"""Test pruning additionalProperties when False."""
⋮----
def test_disable_pruning_additional_properties(self)
⋮----
"""Test disabling pruning of additionalProperties."""
⋮----
result = compress_schema(schema, prune_additional_properties=False)
⋮----
def test_combined_operations(self)
⋮----
"""Test all pruning operations together."""
⋮----
result = compress_schema(schema, prune_params=["remove"])
# Check that parameter was removed
⋮----
# Check that required list was updated
⋮----
# Check that unused definitions were removed
assert "$defs" not in result  # Both defs should be gone
# Check that additionalProperties was removed
⋮----
def test_prune_titles(self)
⋮----
"""Test pruning title fields."""
⋮----
result = compress_schema(schema, prune_titles=True)
⋮----
def test_prune_nested_additional_properties(self)
⋮----
"""Test pruning additionalProperties: false at all levels."""

================
File: tests/utilities/test_logging.py
================
def test_logging_doesnt_affect_other_loggers(caplog)
⋮----
# set FastMCP loggers to CRITICAL and ensure other loggers still emit messages
original_level = logging.getLogger("FastMCP").getEffectiveLevel()
⋮----
root_logger = logging.getLogger()
app_logger = logging.getLogger("app")
fastmcp_logger = logging.getLogger("FastMCP")
fastmcp_server_logger = get_logger("server")

================
File: tests/utilities/test_mcp_config.py
================
def test_parse_single_stdio_config()
⋮----
config = {
mcp_config = MCPConfig.from_dict(config)
transport = mcp_config.mcpServers["test_server"].to_transport()
⋮----
def test_parse_single_remote_config()
def test_parse_remote_config_with_transport()
def test_parse_remote_config_with_url_inference()
def test_parse_multiple_servers()
async def test_multi_client(tmp_path: Path)
⋮----
server_script = inspect.cleandoc("""
script_path = tmp_path / "test.py"
⋮----
client = Client(config)
⋮----
tools = await client.list_tools()
⋮----
result_1 = await client.call_tool("test_1_add", {"a": 1, "b": 2})
result_2 = await client.call_tool("test_2_add", {"a": 1, "b": 2})
⋮----
async def test_remote_config_default_no_auth()
async def test_remote_config_with_auth_token()
async def test_remote_config_sse_with_auth_token()
async def test_remote_config_with_oauth_literal()

================
File: tests/utilities/test_tests.py
================
class TestTemporarySettings
⋮----
def test_temporary_settings(self)

================
File: tests/utilities/test_typeadapter.py
================
"""
This test file adapts tests from test_func_metadata.py which tested a custom implementation
that has been replaced by pydantic TypeAdapters.
The tests ensure our TypeAdapter-based approach covers all the edge cases the old custom
implementation handled. Since we're now using standard pydantic functionality, these tests
may be redundant with pydantic's own tests and could potentially be removed in the future.
"""
⋮----
# Models must be defined at the module level for forward references to work
class SomeInputModelA(BaseModel)
class SomeInputModelB(BaseModel)
⋮----
class InnerModel(BaseModel)
⋮----
x: int
how_many_shrimp: Annotated[int, Field(description="How many shrimp in the tank???")]
ok: InnerModel
y: None
# Define additional models needed in tests
class SomeComplexModel(BaseModel)
⋮----
y: dict[int, str]
⋮----
# list[str] | str is an interesting case because if it comes in as JSON like
# "[\"a\", \"b\"]" then it will be naively parsed as a string.
⋮----
str,  # Should be ignored, really
⋮----
my_model_a_with_default: SomeInputModelA = SomeInputModelA(),  # noqa: B008
⋮----
_ = (
⋮----
def get_simple_func_adapter()
⋮----
"""Get a TypeAdapter for a simple function to avoid forward reference issues"""
def simple_func(x: int, y: str = "default") -> str
⋮----
async def test_complex_function_runtime_arg_validation_non_json()
⋮----
"""Test that basic non-JSON arguments are validated correctly using a simpler function"""
type_adapter = get_simple_func_adapter()
# Test with minimum required arguments
args = {"x": 1}
result = type_adapter.validate_python(args)
⋮----
)  # Don't call result() as TypeAdapter returns the value directly
# Test with all arguments
args = {"x": 1, "y": "hello"}
⋮----
# Test with invalid types
⋮----
def test_missing_annotation()
⋮----
"""Test that missing annotations don't cause errors"""
def func_no_annotations(x, y)
type_adapter = get_cached_typeadapter(func_no_annotations)
result = type_adapter.validate_python({"x": "1", "y": "2"})
assert result == "12"  # String concatenation since no type info
def test_convert_str_to_complex_type()
⋮----
"""Test that string arguments are converted to the complex type when valid"""
def func_with_str_types(string: SomeComplexModel)
# Create a valid model instance
input_data = {"x": 1, "y": {1: "hello"}}
# Validate with model directly
⋮----
# Now check if type adapter validates correctly
type_adapter = get_cached_typeadapter(func_with_str_types)
result = type_adapter.validate_python({"string": input_data})
⋮----
def test_skip_names()
⋮----
"""Test that skipped parameters are not included in the schema"""
⋮----
# Get schema and prune parameters
type_adapter = get_cached_typeadapter(func_with_many_params)
schema = type_adapter.json_schema()
pruned_schema = compress_schema(schema, prune_params=["skip_this", "also_skip"])
# Check that only the desired parameters remain
⋮----
# The pruned parameters should also be removed from required
⋮----
async def test_lambda_function()
⋮----
"""Test lambda function schema and validation"""
fn = lambda x, y=5: str(x)  # noqa: E731
type_adapter = get_cached_typeadapter(fn)
# Basic calls - validate_python returns the result directly
result = type_adapter.validate_python({"x": "hello"})
⋮----
result = type_adapter.validate_python({"x": "hello", "y": "world"})
⋮----
# Missing required arg
⋮----
def test_basic_json_schema()
⋮----
"""Test JSON schema generation for a simple function"""
def simple_func(a: int, b: str = "default") -> str
type_adapter = get_cached_typeadapter(simple_func)
⋮----
# Check basic properties
⋮----
# Check required
⋮----
def test_str_vs_int()
⋮----
"""
    Test that string values are kept as strings even when they contain numbers,
    while numbers are parsed correctly.
    """
def func_with_str_and_int(a: str, b: int)
type_adapter = get_cached_typeadapter(func_with_str_and_int)
result = type_adapter.validate_python({"a": "123", "b": 123})

================
File: tests/utilities/test_types.py
================
class BaseClass
class ChildClass(BaseClass)
class OtherClass
class TestIsClassMemberOfType
⋮----
def test_basic_subclass_check(self)
⋮----
"""Test that a subclass is recognized as a member of the base class."""
⋮----
def test_self_is_member(self)
⋮----
"""Test that a class is a member of itself."""
⋮----
def test_unrelated_class_is_not_member(self)
⋮----
"""Test that an unrelated class is not a member of the base class."""
⋮----
def test_typing_union_with_member_is_member(self)
⋮----
"""Test that Union type with a member class is detected as a member."""
union_type1: Any = ChildClass | OtherClass
union_type2: Any = OtherClass | ChildClass
⋮----
def test_typing_union_without_member_is_not_member(self)
⋮----
"""Test that Union type without any member class is not a member."""
union_type: Any = OtherClass | str
⋮----
def test_pipe_union_with_member_is_member(self)
⋮----
"""Test that pipe syntax union with a member class is detected as a member."""
union_pipe1: Any = ChildClass | OtherClass
union_pipe2: Any = OtherClass | ChildClass
⋮----
def test_pipe_union_without_member_is_not_member(self)
⋮----
"""Test that pipe syntax union without any member class is not a member."""
union_pipe: Any = OtherClass | str
⋮----
def test_annotated_member_is_member(self)
⋮----
"""Test that Annotated with a member class is detected as a member."""
annotated1: Any = Annotated[ChildClass, "metadata"]
annotated2: Any = Annotated[BaseClass, "metadata"]
⋮----
def test_annotated_non_member_is_not_member(self)
⋮----
"""Test that Annotated with a non-member class is not a member."""
annotated: Any = Annotated[OtherClass, "metadata"]
⋮----
def test_annotated_with_union_member_is_member(self)
⋮----
"""Test that Annotated with a Union containing a member class is a member."""
# Test with both Union styles
annotated1: Any = Annotated[ChildClass | OtherClass, "metadata"]
annotated2: Any = Annotated[ChildClass | OtherClass, "metadata"]
⋮----
def test_nested_annotated_with_member_is_member(self)
⋮----
"""Test that nested Annotated with a member class is a member."""
annotated: Any = Annotated[Annotated[ChildClass, "inner"], "outer"]
⋮----
def test_none_is_not_member(self)
⋮----
"""Test that None is not a member of any class."""
assert not is_class_member_of_type(None, BaseClass)  # type: ignore
def test_generic_type_is_not_member(self)
⋮----
"""Test that generic types are not members based on their parameter types."""
list_type: Any = list[ChildClass]
⋮----
class TestIsSubclassSafe
⋮----
def test_child_is_subclass_of_parent(self)
⋮----
"""Test that a child class is recognized as a subclass of its parent."""
⋮----
def test_class_is_subclass_of_itself(self)
⋮----
"""Test that a class is a subclass of itself."""
⋮----
def test_unrelated_class_is_not_subclass(self)
⋮----
"""Test that an unrelated class is not a subclass."""
⋮----
def test_none_type_handled_safely(self)
⋮----
"""Test that None type is handled safely without raising TypeError."""
assert not issubclass_safe(None, BaseClass)  # type: ignore
class TestImage
⋮----
def test_image_initialization_with_path(self)
⋮----
"""Test image initialization with a path."""
# Mock test - we're not actually going to read a file
image = Image(path="test.png")
⋮----
def test_image_initialization_with_data(self)
⋮----
"""Test image initialization with data."""
image = Image(data=b"test")
⋮----
assert image._mime_type == "image/png"  # Default for raw data
def test_image_initialization_with_format(self)
⋮----
"""Test image initialization with a specific format."""
image = Image(data=b"test", format="jpeg")
⋮----
def test_missing_data_and_path_raises_error(self)
⋮----
"""Test that error is raised when neither path nor data is provided."""
⋮----
def test_both_data_and_path_raises_error(self)
⋮----
"""Test that error is raised when both path and data are provided."""
⋮----
def test_get_mime_type_from_path(self, tmp_path)
⋮----
"""Test MIME type detection from file extension."""
extensions = {
⋮----
path = tmp_path / f"test{ext}"
⋮----
img = Image(path=path)
⋮----
def test_to_image_content(self, tmp_path, monkeypatch)
⋮----
"""Test conversion to ImageContent."""
# Test with path
img_path = tmp_path / "test.png"
test_data = b"fake image data"
⋮----
img = Image(path=img_path)
content = img.to_image_content()
⋮----
# Test with data
img = Image(data=test_data, format="jpeg")
⋮----
def test_to_image_content_error(self, monkeypatch)
⋮----
"""Test error case in to_image_content."""
# Create an Image with neither path nor data (shouldn't happen due to __init__ checks,
# but testing the method's own error handling)
img = Image(data=b"test")
⋮----
class TestAudio
⋮----
def test_audio_initialization_with_path(self)
⋮----
"""Test audio initialization with a path."""
⋮----
audio = Audio(path="test.wav")
⋮----
def test_audio_initialization_with_data(self)
⋮----
"""Test audio initialization with data."""
audio = Audio(data=b"test")
⋮----
assert audio._mime_type == "audio/wav"  # Default for raw data
def test_audio_initialization_with_format(self)
⋮----
"""Test audio initialization with a specific format."""
audio = Audio(data=b"test", format="mp3")
⋮----
audio = Audio(path=path)
⋮----
def test_to_audio_content(self, tmp_path, monkeypatch)
⋮----
"""Test conversion to AudioContent."""
⋮----
audio_path = tmp_path / "test.wav"
test_data = b"fake audio data"
⋮----
audio = Audio(path=audio_path)
content = audio.to_audio_content()
⋮----
audio = Audio(data=test_data, format="mp3")
⋮----
def test_to_audio_content_error(self, monkeypatch)
⋮----
"""Test error case in to_audio_content."""
# Create an Audio with neither path nor data (shouldn't happen due to __init__ checks,
⋮----
def test_to_audio_content_with_override_mime_type(self, tmp_path)
⋮----
"""Test conversion to AudioContent with override MIME type."""
⋮----
content = audio.to_audio_content(mime_type="audio/custom")
⋮----
class TestFile
⋮----
def test_file_initialization_with_path(self)
⋮----
"""Test file initialization with a path."""
⋮----
file = File(path="test.txt")
⋮----
def test_file_initialization_with_data(self)
⋮----
"""Test initialization with data and format."""
test_data = b"test data"
file = File(data=test_data, format="octet-stream")
⋮----
# The format parameter should set the MIME type
⋮----
def test_file_initialization_with_format(self)
⋮----
"""Test file initialization with a specific format."""
file = File(data=b"test", format="pdf")
⋮----
def test_file_initialization_with_name(self)
⋮----
"""Test file initialization with a custom name."""
file = File(data=b"test", name="custom")
⋮----
file_path = tmp_path / "test.txt"
⋮----
)  # Need to write content for MIME type detection
file = File(path=file_path)
# The MIME type should be detected from the .txt extension
⋮----
def test_to_resource_content_with_path(self, tmp_path)
⋮----
"""Test conversion to ResourceContent with path."""
⋮----
test_data = b"test file data"
⋮----
resource = file.to_resource_content()
⋮----
# Convert both to strings for comparison
⋮----
def test_to_resource_content_with_data(self)
⋮----
"""Test conversion to ResourceContent with data."""
⋮----
file = File(data=test_data, format="pdf")
⋮----
# Convert URI to string for comparison
⋮----
def test_to_resource_content_with_text_data(self)
⋮----
"""Test conversion to ResourceContent with text data (TextResourceContents)."""
test_data = b"hello world"
file = File(data=test_data, format="plain")
⋮----
# Should be TextResourceContents for text/plain
⋮----
def test_to_resource_content_error(self, monkeypatch)
⋮----
"""Test error case in to_resource_content."""
file = File(data=b"test")
⋮----
def test_to_resource_content_with_override_mime_type(self, tmp_path)
⋮----
"""Test conversion to ResourceContent with override MIME type."""
⋮----
resource = file.to_resource_content(mime_type="application/custom")
⋮----
class TestFindKwargByType
⋮----
def test_exact_type_match(self)
⋮----
"""Test finding parameter with exact type match."""
def func(a: int, b: str, c: BaseClass)
⋮----
def test_no_matching_parameter(self)
⋮----
"""Test finding parameter when no match exists."""
def func(a: int, b: str, c: OtherClass)
⋮----
def test_parameter_with_no_annotation(self)
⋮----
"""Test with a parameter that has no type annotation."""
def func(a: int, b, c: BaseClass)
⋮----
def test_union_type_match_pipe_syntax(self)
⋮----
"""Test finding parameter with union type using pipe syntax."""
def func(a: int, b: str | BaseClass, c: str)
⋮----
def test_union_type_match_typing_union(self)
⋮----
"""Test finding parameter with union type using Union."""
⋮----
def test_annotated_type_match(self)
⋮----
"""Test finding parameter with Annotated type."""
def func(a: int, b: Annotated[BaseClass, "metadata"], c: str)
⋮----
def test_method_parameter(self)
⋮----
"""Test finding parameter in a class method."""
class TestClass
⋮----
def method(self, a: int, b: BaseClass)
instance = TestClass()
⋮----
def test_static_method_parameter(self)
⋮----
"""Test finding parameter in a static method."""
⋮----
@staticmethod
            def static_method(a: int, b: BaseClass, c: str)
⋮----
def test_class_method_parameter(self)
⋮----
@classmethod
            def class_method(cls, a: int, b: BaseClass, c: str)
⋮----
def test_multiple_matching_parameters(self)
⋮----
"""Test finding first parameter when multiple matches exist."""
def func(a: BaseClass, b: str, c: BaseClass)
# Should return the first match
⋮----
def test_subclass_match(self)
⋮----
"""Test finding parameter with a subclass of the target type."""
def func(a: int, b: ChildClass, c: str)
⋮----
def test_nonstandard_annotation(self)
⋮----
"""Test finding parameter with a nonstandard annotation like an
        instance. This is irregular."""
SENTINEL = object()
def func(a: int, b: SENTINEL, c: str):  # type: ignore
assert find_kwarg_by_type(func, SENTINEL) is None  # type: ignore
def test_ellipsis_annotation(self)
⋮----
"""Test finding parameter with an ellipsis annotation."""
def func(a: int, b: EllipsisType, c: str):  # type: ignore  # noqa: F821
assert find_kwarg_by_type(func, EllipsisType) == "b"  # type: ignore
def test_missing_type_annotation(self)
⋮----
"""Test finding parameter with a missing type annotation."""
def func(a: int, b, c: str)
⋮----
class TestReplaceType
⋮----
),  # list[int] will match before int
⋮----
def test_replace_type(self, input, type_map, expected)
⋮----
"""Test replacing a type with another type."""

================
File: tests/test_examples.py
================
"""Tests for example servers"""
⋮----
async def test_simple_echo()
⋮----
"""Test the simple echo server"""
⋮----
result = await client.call_tool_mcp("echo", {"text": "hello"})
⋮----
assert result.content[0].text == "hello"  # type: ignore[attr-defined]
async def test_complex_inputs()
⋮----
"""Test the complex inputs server"""
⋮----
tank = {"shrimp": [{"name": "bob"}, {"name": "alice"}]}
result = await client.call_tool_mcp(
⋮----
assert result.content[0].text == '[\n  "bob",\n  "alice",\n  "charlie"\n]'  # type: ignore[attr-defined]
async def test_desktop(monkeypatch)
⋮----
"""Test the desktop server"""
⋮----
# Test the add function
result = await client.call_tool_mcp("add", {"a": 1, "b": 2})
⋮----
assert result.content[0].text == "3"  # type: ignore[attr-defined]
⋮----
result = await client.read_resource(AnyUrl("greeting://rooter12"))
⋮----
assert result[0].text == "Hello, rooter12!"  # type: ignore[attr-defined]
async def test_echo()
⋮----
"""Test the echo server"""
⋮----
result = await client.call_tool_mcp("echo_tool", {"text": "hello"})
⋮----
result = await client.read_resource(AnyUrl("echo://static"))
⋮----
assert result[0].text == "Echo!"  # type: ignore[attr-defined]
⋮----
result = await client.read_resource(AnyUrl("echo://server42"))
⋮----
assert result[0].text == "Echo: server42"  # type: ignore[attr-defined]
⋮----
result = await client.get_prompt("echo", {"text": "hello"})
⋮----
assert result.messages[0].content.text == "hello"  # type: ignore[attr-defined]

================
File: .gitignore
================
# Python-generated files
__pycache__/
*.py[cod]
*$py.class
build/
dist/
wheels/
*.egg-info/
*.egg
MANIFEST
.pytest_cache/
.coverage
htmlcov/
.tox/
nosetests.xml
coverage.xml
*.cover

# Virtual environments
.venv
venv/
env/
ENV/
.env

# System files
.DS_Store

# Version file
src/fastmcp/_version.py

# Editors and IDEs
.cursorrules
.vscode/
.idea/
*.swp
*.swo
*~
.project
.pydevproject
.settings/

# Jupyter Notebook
.ipynb_checkpoints

# Type checking
.mypy_cache/
.dmypy.json
dmypy.json
.pyre/
.pytype/

# Local development
.python-version
.envrc
.direnv/

# Logs and databases
*.log
*.sqlite
*.db
*.ddb

================
File: .pre-commit-config.yaml
================
fail_fast: false
repos:
  - repo: https://github.com/abravalheri/validate-pyproject
    rev: v0.23
    hooks:
      - id: validate-pyproject
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [yaml, json5]
  - repo: https://github.com/astral-sh/ruff-pre-commit
    # Ruff version.
    rev: v0.11.4
    hooks:
      # Run the linter.
      - id: ruff
        args: [--fix, --exit-non-zero-on-fix]
      # Run the formatter.
      - id: ruff-format
  - repo: https://github.com/northisup/pyright-pretty
    rev: v0.1.0
    hooks:
      - id: pyright-pretty
        files: ^src/|^tests/

================
File: AGENTS.md
================
# AGENTS

> **Audience**: LLM-driven engineering agents

This file provides guidance for autonomous coding agents working inside the **FastMCP** repository.

---

## Repository map

| Path             | Purpose                                                                                  |
| ---------------- | ---------------------------------------------------------------------------------------- |
| `src/fastmcp/`   | Library source code (Python ≥ 3.10)                                                      |
| `  └─server/`    | Server implementation, `FastMCP`, auth, networking                                       |
| `  └─client/`    | High‑level client SDK + helpers                                                          |
| `  └─resources/` | MCP resources and resource templates                                                     |
| `  └─prompts/`   | Prompt templates                                                                         |
| `  └─tools/`     | Tool implementations                                                                     |
| `tests/`         | Pytest test‑suite                                                                        |
| `docs/`          | Mintlify‑flavoured Markdown, published to [https://gofastmcp.com](https://gofastmcp.com) |
| `examples/`      | Minimal runnable demos                                                                   |

---

## Mandatory dev workflow

```bash
uv sync                              # install dependencies
uv run pre-commit run --all-files    # Ruff + Prettier + Pyright
uv run pytest                        # run full test suite
```

*Tests must pass* and *lint/typing must be clean* before committing.

### Core MCP objects

There are four major MCP object types:

- Tools (`src/tools/`)
- Resources (`src/resources/`)
- Resource Templates (`src/resources/`)
- Prompts (`src/prompts`)

While these have slightly different semantics and implementations, in general changes that affect interactions with any one (like adding tags, importing, etc.) will need to be adopted, applied, and tested on all others. Be sure to look at not only the object definition but also the related `Manager` (e.g. `ToolManager`, `ResourceManager`, and `PromptManager`). Also note that while resources and resource templates are different objects, they both are handled by the `ResourceManager`.

---

## Code conventions

* **Language:** Python ≥ 3.10
* **Style:** Enforced through pre-commit hooks
* **Type-checking:** Fully typed codebase
* **Tests:** Each feature should have corresponding tests

---

## Development guidelines

1. **Set up** the environment:
   ```bash
   uv sync && uv run pre-commit run --all-files
   ```
2. **Run tests**: `uv run pytest` until they pass.
3. **Iterate**: if a command fails, read the output, fix the code, retry.
4. Make the smallest set of changes that achieve the desired outcome.
5. Always read code before modifying it blindly.
6. Follow established patterns and maintain consistency.

================
File: CLAUDE.md
================
# FastMCP Development Guidelines

## Documentation

- Documentation uses the Mintlify framework
- Files must be present in docs.json to be included in the documentation

## Testing and Investigation

### In-Memory Transport - Always Preferred

When testing or investigating FastMCP servers, **always prefer the in-memory transport** unless you specifically need HTTP transport features. Pass a FastMCP server directly to a Client to eliminate separate processes and network complexity.

```python
# Create your FastMCP server
mcp = FastMCP("TestServer")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

# Pass server directly to client - uses in-memory transport
async with Client(mcp) as client:
    result = await client.call_tool("greet", {"name": "World"})
```

### When to Use HTTP Transport

Only use HTTP transport when testing network-specific features. Prefer StreamableHttp over SSE as it's the modern approach.

```python
# Only when network testing is required
async with Client(transport=StreamableHttpTransport(server_url)) as client:
    result = await client.ping()
```

## Development Workflow

- You must always run pre-commit if you open a PR, because it is run as part of a required check.
- When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.
- NEVER modify files in docs/python-sdk/**, as they are auto-generated.
- Use # type: ignore[attr-defined] in unit tests when accessing an MCP result of indeterminate type instead of asserting its type

================
File: justfile
================
# Build the project
build:
    uv sync

# Run tests
test: build
    uv run --frozen pytest -xvs tests

# Run pyright on all files
typecheck:
    uv run --frozen pyright

# Serve documentation locally
docs:
    cd docs && npx mint@latest dev

# Generate API reference documentation for all modules
api-ref-all:
    uvx --with-editable . --refresh-package mdxify mdxify@latest --all --root-module fastmcp --anchor-name "Python SDK" --exclude fastmcp.contrib

# Generate API reference for specific modules (e.g., just api-ref prefect.flows prefect.tasks)
api-ref *MODULES:
    uvx --with-editable . --refresh-package mdxify mdxify@latest {{MODULES}} --root-module fastmcp --anchor-name "Python SDK"

# Clean up API reference documentation
api-ref-clean:
    rm -rf docs/python-sdk

copy-context:
    uvx --with-editable . --refresh-package copychat copychat@latest src/ docs/ -x changelog.mdx -x python-sdk/ -v

================
File: LICENSE
================
Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================
File: pyproject.toml
================
[project]
name = "fastmcp"
dynamic = ["version"]
description = "The fast, Pythonic way to build MCP servers."
authors = [{ name = "Jeremiah Lowin" }]
dependencies = [
    "python-dotenv>=1.1.0",
    "exceptiongroup>=1.2.2",
    "httpx>=0.28.1",
    "mcp>=1.10.0",
    "openapi-pydantic>=0.5.1",
    "rich>=13.9.4",
    "typer>=0.15.2",
    "authlib>=1.5.2",
    "pydantic[email]>=2.11.7",
]
requires-python = ">=3.10"
readme = "README.md"
license = "Apache-2.0"

keywords = [
    "mcp",
    "mcp server",
    "mcp client",
    "model context protocol",
    "fastmcp",
    "llm",
    "agent",
]
classifiers = [
    "Intended Audience :: Developers",
    "License :: OSI Approved :: Apache Software License",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Typing :: Typed",
]

[dependency-groups]
dev = [
    "copychat>=0.5.2",
    "dirty-equals>=0.9.0",
    "fastapi>=0.115.12",
    "ipython>=8.12.3",
    "pdbpp>=0.10.3",
    "pre-commit",
    "pyinstrument>=5.0.2",
    "pyright>=1.1.389",
    "pytest>=8.3.3",
    "pytest-asyncio>=0.23.5",
    "pytest-cov>=6.1.1",
    "pytest-env>=1.1.5",
    "pytest-flakefinder",
    "pytest-httpx>=0.35.0",
    "pytest-report>=0.2.1",
    "pytest-timeout>=2.4.0",
    "pytest-xdist>=3.6.1",
    "ruff",
]

[project.scripts]
fastmcp = "fastmcp.cli:app"

[project.urls]
Homepage = "https://gofastmcp.com"
Repository = "https://github.com/jlowin/fastmcp"
Documentation = "https://gofastmcp.com"

[project.optional-dependencies]
websockets = ["websockets>=15.0.1"]


[build-system]
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "uv-dynamic-versioning"

[tool.hatch.metadata]
allow-direct-references = true

[tool.uv-dynamic-versioning]
vcs = "git"
style = "pep440"
bump = true
fallback-version = "0.0.0"


[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
# filterwarnings = ["error::DeprecationWarning"]
timeout = 3
env = [
    "FASTMCP_TEST_MODE=1",
    'D:FASTMCP_LOG_LEVEL=DEBUG',
    'D:FASTMCP_ENABLE_RICH_TRACEBACKS=0',
]

[tool.pyright]
include = ["src", "tests"]
exclude = ["**/node_modules", "**/__pycache__", ".venv", ".git", "dist"]
pythonVersion = "3.10"
pythonPlatform = "Darwin"
typeCheckingMode = "basic"
reportMissingImports = true
reportMissingTypeStubs = false
useLibraryCodeForTypes = true
venvPath = "."
venv = ".venv"
strict = ["src/fastmcp/server/server.py"]

[tool.ruff.lint]
extend-select = ["I", "UP"]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "I001", "RUF013"]
# allow imports not at the top of the file
"src/fastmcp/__init__.py" = ["E402"]

================
File: README.md
================
<div align="center">

<!-- omit in toc -->
# FastMCP v2 🚀

<strong>The fast, Pythonic way to build MCP servers and clients.</strong>

*FastMCP is made with 💙 by [Prefect](https://www.prefect.io/)*

[![Docs](https://img.shields.io/badge/docs-gofastmcp.com-blue)](https://gofastmcp.com)
[![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
[![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
[![License](https://img.shields.io/github/license/jlowin/fastmcp.svg)](https://github.com/jlowin/fastmcp/blob/main/LICENSE)

<a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="jlowin%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>

> [!Note]
>
> #### Beyond the Protocol
>
> FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
>
> This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.
>
> FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.
>
> Ready to upgrade or get started? Follow the [installation instructions](https://gofastmcp.com/getting-started/installation), which include steps for upgrading from the official MCP SDK.

---

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code.

```python
# server.py
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()
```

Run the server locally:

```bash
fastmcp run server.py
```

### 📚 Documentation

FastMCP's complete documentation is available at **[gofastmcp.com](https://gofastmcp.com)**, including detailed guides, API references, and advanced patterns. This readme provides only a high-level overview.

Documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.

There are two ways to access the LLM-friendly documentation:

- [`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.

---

<!-- omit in toc -->
## Table of Contents

- [What is MCP?](#what-is-mcp)
- [Why FastMCP?](#why-fastmcp)
- [Installation](#installation)
- [Core Concepts](#core-concepts)
  - [The `FastMCP` Server](#the-fastmcp-server)
  - [Tools](#tools)
  - [Resources \& Templates](#resources--templates)
  - [Prompts](#prompts)
  - [Context](#context)
  - [MCP Clients](#mcp-clients)
- [Advanced Features](#advanced-features)
  - [Proxy Servers](#proxy-servers)
  - [Composing MCP Servers](#composing-mcp-servers)
  - [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
  - [Authentication \& Security](#authentication--security)
- [Running Your Server](#running-your-server)
- [Contributing](#contributing)
  - [Prerequisites](#prerequisites)
  - [Setup](#setup)
  - [Unit Tests](#unit-tests)
  - [Static Checks](#static-checks)
  - [Pull Requests](#pull-requests)

---

## What is MCP?

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:

- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
- And more!

FastMCP provides a high-level, Pythonic interface for building, managing, and interacting with these servers.

## Why FastMCP?

The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.

FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.

FastMCP aims to be:

🚀 **Fast:** High-level interface means less code and faster development

🍀 **Simple:** Build MCP servers with minimal boilerplate

🐍 **Pythonic:** Feels natural to Python developers

🔍 **Complete:** A comprehensive platform for all MCP use cases, from dev to prod

## Installation

We recommend installing FastMCP with [uv](https://docs.astral.sh/uv/):

```bash
uv pip install fastmcp
```

For full installation instructions, including verification, upgrading from the official MCPSDK, and developer setup, see the [**Installation Guide**](https://gofastmcp.com/getting-started/installation).

## Core Concepts

These are the building blocks for creating MCP servers and clients with FastMCP.

### The `FastMCP` Server

The central object representing your MCP application. It holds your tools, resources, and prompts, manages connections, and can be configured with settings like authentication.

```python
from fastmcp import FastMCP

# Create a server instance
mcp = FastMCP(name="MyAssistantServer")
```

Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/servers/fastmcp).

### Tools

Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images or audio aided by the FastMCP media helper classes.

```python
@mcp.tool
def multiply(a: float, b: float) -> float:
    """Multiplies two numbers."""
    return a * b
```

Learn more in the [**Tools Documentation**](https://gofastmcp.com/servers/tools).

### Resources & Templates

Resources expose read-only data sources (like `GET` requests). Use `@mcp.resource("your://uri")`. Use `{placeholders}` in the URI to create dynamic templates that accept parameters, allowing clients to request specific data subsets.

```python
# Static resource
@mcp.resource("config://version")
def get_version(): 
    return "2.0.1"

# Dynamic resource template
@mcp.resource("users://{user_id}/profile")
def get_profile(user_id: int):
    # Fetch profile for user_id...
    return {"name": f"User {user_id}", "status": "active"}
```

Learn more in the [**Resources & Templates Documentation**](https://gofastmcp.com/servers/resources).

### Prompts

Prompts define reusable message templates to guide LLM interactions. Decorate functions with `@mcp.prompt`. Return strings or `Message` objects.

```python
@mcp.prompt
def summarize_request(text: str) -> str:
    """Generate a prompt asking for a summary."""
    return f"Please summarize the following text:\n\n{text}"
```

Learn more in the [**Prompts Documentation**](https://gofastmcp.com/servers/prompts).

### Context

Access MCP session capabilities within your tools, resources, or prompts by adding a `ctx: Context` parameter. Context provides methods for:

- **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
- **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
- **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
- **Resource Access:** Use `ctx.read_resource()` to access resources on the server
- **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
- and more...

To access the context, add a parameter annotated as `Context` to any mcp-decorated function. FastMCP will automatically inject the correct context object when the function is called.

```python
from fastmcp import FastMCP, Context

mcp = FastMCP("My MCP Server")

@mcp.tool
async def process_data(uri: str, ctx: Context):
    # Log a message to the client
    await ctx.info(f"Processing {uri}...")

    # Read a resource from the server
    data = await ctx.read_resource(uri)

    # Ask client LLM to summarize the data
    summary = await ctx.sample(f"Summarize: {data.content[:500]}")

    # Return the summary
    return summary.text
```

Learn more in the [**Context Documentation**](https://gofastmcp.com/servers/context).

### MCP Clients

Interact with *any* MCP server programmatically using the `fastmcp.Client`. It supports various transports (Stdio, SSE, In-Memory) and often auto-detects the correct one. The client can also handle advanced patterns like server-initiated **LLM sampling requests** if you provide an appropriate handler.

Critically, the client allows for efficient **in-memory testing** of your servers by connecting directly to a `FastMCP` server instance via the `FastMCPTransport`, eliminating the need for process management or network calls during tests.

```python
from fastmcp import Client

async def main():
    # Connect via stdio to a local script
    async with Client("my_server.py") as client:
        tools = await client.list_tools()
        print(f"Available tools: {tools}")
        result = await client.call_tool("add", {"a": 5, "b": 3})
        print(f"Result: {result.text}")

    # Connect via SSE
    async with Client("http://localhost:8000/sse") as client:
        # ... use the client
        pass
```

To use clients to test servers, use the following pattern:

```python
from fastmcp import FastMCP, Client

mcp = FastMCP("My MCP Server")

async def main():
    # Connect via in-memory transport
    async with Client(mcp) as client:
        # ... use the client
```

FastMCP also supports connecting to multiple servers through a single unified client using the standard MCP configuration format:

```python
from fastmcp import Client

# Standard MCP configuration with multiple servers
config = {
    "mcpServers": {
        "weather": {"url": "https://weather-api.example.com/mcp"},
        "assistant": {"command": "python", "args": ["./assistant_server.py"]}
    }
}

# Create a client that connects to all servers
client = Client(config)

async def main():
    async with client:
        # Access tools and resources with server prefixes
        forecast = await client.call_tool("weather_get_forecast", {"city": "London"})
        answer = await client.call_tool("assistant_answer_question", {"query": "What is MCP?"})
```

Learn more in the [**Client Documentation**](https://gofastmcp.com/clients/client) and [**Transports Documentation**](https://gofastmcp.com/clients/transports).

## Advanced Features

FastMCP introduces powerful ways to structure and deploy your MCP applications.

### Proxy Servers

Create a FastMCP server that acts as an intermediary for another local or remote MCP server using `FastMCP.as_proxy()`. This is especially useful for bridging transports (e.g., remote SSE to local Stdio) or adding a layer of logic to a server you don't control.

Learn more in the [**Proxying Documentation**](https://gofastmcp.com/patterns/proxy).

### Composing MCP Servers

Build modular applications by mounting multiple `FastMCP` instances onto a parent server using `mcp.mount()` (live link) or `mcp.import_server()` (static copy).

Learn more in the [**Composition Documentation**](https://gofastmcp.com/patterns/composition).

### OpenAPI & FastAPI Generation

Automatically generate FastMCP servers from existing OpenAPI specifications (`FastMCP.from_openapi()`) or FastAPI applications (`FastMCP.from_fastapi()`), instantly bringing your web APIs to the MCP ecosystem.

Learn more: [**OpenAPI Integration**](https://gofastmcp.com/servers/openapi#openapi-integration) | [**FastAPI Integration**](https://gofastmcp.com/deployment/asgi#fastapi-integration).

### Authentication & Security

FastMCP provides built-in authentication support to secure both your MCP servers and clients in production environments. Protect your server endpoints from unauthorized access and authenticate your clients against secured MCP servers using industry-standard protocols.

- **Server Protection**: Secure your FastMCP server endpoints with configurable authentication providers
- **Client Authentication**: Connect to authenticated MCP servers with automatic credential management
- **Production Ready**: Support for common authentication patterns used in enterprise environments

Learn more in the **Authentication Documentation** for [servers](https://gofastmcp.com/servers/auth) and [clients](https://gofastmcp.com/clients/auth).

## Running Your Server

The main way to run a FastMCP server is by calling the `run()` method on your server instance:

```python
# server.py
from fastmcp import FastMCP

mcp = FastMCP("Demo 🚀")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()  # Default: uses STDIO transport
```

FastMCP supports three transport protocols:

**STDIO (Default)**: Best for local tools and command-line scripts.

```python
mcp.run(transport="stdio")  # Default, so transport argument is optional
```

**Streamable HTTP**: Recommended for web deployments.

```python
mcp.run(transport="http", host="127.0.0.1", port=8000, path="/mcp")
```

**SSE**: For compatibility with existing SSE clients.

```python
mcp.run(transport="sse", host="127.0.0.1", port=8000)
```

See the [**Running Server Documentation**](https://gofastmcp.com/deployment/running-server) for more details.

## Contributing

Contributions are the core of open source! We welcome improvements and features.

### Prerequisites

- Python 3.10+
- [uv](https://docs.astral.sh/uv/) (Recommended for environment management)

### Setup

1. Clone the repository:

   ```bash
   git clone https://github.com/jlowin/fastmcp.git 
   cd fastmcp
   ```

2. Create and sync the environment:

   ```bash
   uv sync
   ```

   This installs all dependencies, including dev tools.

3. Activate the virtual environment (e.g., `source .venv/bin/activate` or via your IDE).

### Unit Tests

FastMCP has a comprehensive unit test suite. All PRs must introduce or update tests as appropriate and pass the full suite.

Run tests using pytest:

```bash
pytest
```

or if you want an overview of the code coverage

```bash
uv run pytest --cov=src --cov=examples --cov-report=html
```

### Static Checks

FastMCP uses `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).

Install the hooks locally:

```bash
uv run pre-commit install
```

The hooks will now run automatically on `git commit`. You can also run them manually at any time:

```bash
pre-commit run --all-files
# or via uv
uv run pre-commit run --all-files
```

### Pull Requests

1. Fork the repository on GitHub.
2. Create a feature branch from `main`.
3. Make your changes, including tests and documentation updates.
4. Ensure tests and pre-commit hooks pass.
5. Commit your changes and push to your fork.
6. Open a pull request against the `main` branch of `jlowin/fastmcp`.

Please open an issue or discussion for questions or suggestions before starting significant work!

================
File: Windows_Notes.md
================
# Getting your development environment set up properly
To get your environment up and running properly, you'll need a slightly different set of commands that are windows specific:
```bash
uv venv
.venv\Scripts\activate
uv pip install -e ".[dev]"
```

This will install the package in editable mode, and install the development dependencies.


# Fixing `AttributeError: module 'collections' has no attribute 'Callable'`
- open `.venv\Lib\site-packages\pyreadline\py3k_compat.py`
- change `return isinstance(x, collections.Callable)` to 
``` 
from collections.abc import Callable
return isinstance(x, Callable)
```

# Helpful notes
For developing FastMCP
## Install local development version of FastMCP into a local FastMCP project server
- ensure
- change directories to your FastMCP Server location so you can install it in your .venv
- run `.venv\Scripts\activate` to activate your virtual environment
- Then run a series of commands to uninstall the old version and install the new
```bash
# First uninstall
uv pip uninstall fastmcp

# Clean any build artifacts in your fastmcp directory
cd C:\path\to\fastmcp
del /s /q *.egg-info

# Then reinstall in your weather project
cd C:\path\to\new\fastmcp_server
uv pip install --no-cache-dir -e C:\Users\justj\PycharmProjects\fastmcp

# Check that it installed properly and has the correct git hash
pip show fastmcp
```

## Running the FastMCP server with Inspector
MCP comes with a node.js application called Inspector that can be used to inspect the FastMCP server. To run the inspector, you'll need to install node.js and npm. Then you can run the following commands:
```bash
fastmcp dev server.py
```
This will launch a web app on http://localhost:5173/ that you can use to inspect the FastMCP server.

## If you start development before creating a fork - your get out of jail free card
- Add your fork as a new remote to your local repository `git remote add fork git@github.com:YOUR-USERNAME/REPOSITORY-NAME.git`
  - This will add your repo, short named 'fork', as a remote to your local repository
- Verify that it was added correctly by running `git remote -v`
- Commit your changes
- Push your changes to your fork `git push fork <branch>`
- Create your pull request on GitHub




================================================================
End of Codebase
================================================================
