Skip to content

Commit

Permalink
Merge pull request #34 from jlowin/dependencies
Browse files Browse the repository at this point in the history
Support Python dependencies in Server
  • Loading branch information
jlowin authored Dec 2, 2024
2 parents 5aa6bff + 709b48b commit 03cc8b1
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 130 deletions.
148 changes: 86 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,20 @@ FastMCP handles all the complex protocol details and server management, so you c
- [Prompts](#prompts)
- [Images](#images)
- [Context](#context)
- [Deployment](#deployment)
- [Development](#development)
- [Environment Variables](#environment-variables)
- [Claude Desktop](#claude-desktop)
- [Environment Variables](#environment-variables-1)
- [Running Your Server](#running-your-server)
- [Development Mode (Recommended for Building \& Testing)](#development-mode-recommended-for-building--testing)
- [Claude Desktop Integration (For Regular Use)](#claude-desktop-integration-for-regular-use)
- [Direct Execution (For Advanced Use Cases)](#direct-execution-for-advanced-use-cases)
- [Server Object Names](#server-object-names)
- [Examples](#examples)
- [Echo Server](#echo-server)
- [SQLite Explorer](#sqlite-explorer)
- [Contributing](#contributing)
- [Prerequisites](#prerequisites)
- [Installation](#installation-1)
- [Testing](#testing)
- [Formatting](#formatting)
- [Opening a Pull Request](#opening-a-pull-request)

## Installation

Expand Down Expand Up @@ -154,8 +159,10 @@ mcp = FastMCP("My App")

# Configure host/port for HTTP transport (optional)
mcp = FastMCP("My App", host="localhost", port=8000)

# Specify dependencies for deployment and development
mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
```
*Note: All of the following code examples assume you've created a FastMCP server instance called `mcp`, as shown above.*

### Resources

Expand Down Expand Up @@ -284,84 +291,101 @@ The Context object provides:
- Resource access through `read_resource()`
- Request metadata via `request_id` and `client_id`

## Deployment

The FastMCP CLI helps you develop and deploy MCP servers.
## Running Your Server

Note that for all deployment commands, you are expected to provide the fully qualified path to your server object. For example, if you have a file `server.py` that contains a FastMCP server named `my_server`, you would provide `path/to/server.py:my_server`.
There are three main ways to use your FastMCP server, each suited for different stages of development:

If your server variable has one of the standard names (`mcp`, `server`, or `app`), you can omit the server name from the path and just provide the file: `path/to/server.py`.
### Development Mode (Recommended for Building & Testing)

### Development
The fastest way to test and debug your server is with the MCP Inspector:

Test and debug your server with the MCP Inspector:
```bash
# Provide the fully qualified path to your server
fastmcp dev server.py:my_mcp_server

# Or just the file if your server is named 'mcp', 'server', or 'app'
fastmcp dev server.py
```

Your server is run in an isolated environment, so you'll need to indicate any dependencies with the `--with` flag. FastMCP is automatically included. If you are working on a uv project, you can use the `--with-editable` flag to mount your current directory:
This launches a web interface where you can:
- Test your tools and resources interactively
- See detailed logs and error messages
- Monitor server performance
- Set environment variables for testing

```bash
# With additional packages
fastmcp dev server.py --with pandas --with numpy
During development, you can:
- Add dependencies with `--with`:
```bash
fastmcp dev server.py --with pandas --with numpy
```
- Mount your local code for live updates:
```bash
fastmcp dev server.py --with-editable .
```

# Using your project's dependencies and up-to-date code
fastmcp dev server.py --with-editable .
```
### Claude Desktop Integration (For Regular Use)

#### Environment Variables
Once your server is ready, install it in Claude Desktop to use it with Claude:

The MCP Inspector runs servers in an isolated environment. Environment variables must be set through the Inspector UI and are not inherited from your system. The Inspector does not currently support setting environment variables via command line (see [Issue #94](https://github.com/modelcontextprotocol/inspector/issues/94)).

### Claude Desktop

Install your server in Claude Desktop:
```bash
# Basic usage (name is taken from your FastMCP instance)
fastmcp install server.py
```

Your server will run in an isolated environment with:
- Automatic installation of dependencies specified in your FastMCP instance:
```python
mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
```
- Custom naming via `--name`:
```bash
fastmcp install server.py --name "My Analytics Server"
```
- Environment variable management:
```bash
# Set variables individually
fastmcp install server.py -e API_KEY=abc123 -e DB_URL=postgres://...

# Or load from a .env file
fastmcp install server.py -f .env
```

### Direct Execution (For Advanced Use Cases)

For advanced scenarios like custom deployments or running without Claude, you can execute your server directly:

# With a custom name
fastmcp install server.py --name "My Server"
```python
from fastmcp import FastMCP

# With dependencies
fastmcp install server.py --with pandas --with numpy
mcp = FastMCP("My App")

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

The server name in Claude will be:
1. The `--name` parameter if provided
2. The `name` from your FastMCP instance
3. The filename if the server can't be imported
Run it with:
```bash
# Using the FastMCP CLI
fastmcp run server.py

#### Environment Variables
# Or with Python/uv directly
python server.py
uv run python server.py
```

Claude Desktop runs servers in an isolated environment. Environment variables from your system are NOT automatically available to the server - you must explicitly provide them during installation:

```bash
# Single env var
fastmcp install server.py -e API_KEY=abc123
Note: When running directly, you are responsible for ensuring all dependencies are available in your environment. Any dependencies specified on the FastMCP instance are ignored.

# Multiple env vars
fastmcp install server.py -e API_KEY=abc123 -e OTHER_VAR=value
Choose this method when you need:
- Custom deployment configurations
- Integration with other services
- Direct control over the server lifecycle

# Load from .env file
fastmcp install server.py -f .env
```
### Server Object Names

Environment variables persist across reinstalls and are only updated when new values are provided:
All FastMCP commands will look for a server object called `mcp`, `app`, or `server` in your file. If you have a different object name or multiple servers in one file, use the syntax `server.py:my_server`:

```bash
# First install
fastmcp install server.py -e FOO=bar -e BAZ=123

# Second install - FOO and BAZ are preserved
fastmcp install server.py -e NEW=value
# Using a standard name
fastmcp run server.py

# Third install - FOO gets new value, others preserved
fastmcp install server.py -e FOO=newvalue
# Using a custom name
fastmcp run server.py:my_custom_server
```

## Examples
Expand Down Expand Up @@ -437,11 +461,11 @@ What insights can you provide about the structure and relationships?"""

<summary><h3>Open Developer Guide</h3></summary>

#### Prerequisites
### Prerequisites

FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).

#### Installation
### Installation

Create a fork of this repository, then clone it:

Expand All @@ -460,7 +484,7 @@ uv sync --frozen --all-extras --dev



#### Testing
### Testing

Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.

Expand All @@ -471,7 +495,7 @@ Run tests from the root directory:
pytest -vv
```

#### Formatting
### Formatting

FastMCP enforces a variety of required formats, which you can automatically enforce with pre-commit.

Expand All @@ -487,7 +511,7 @@ The hooks will now run on every commit (as well as on every PR). To run them man
pre-commit run --all-files
```

#### Opening a Pull Request
### Opening a Pull Request

Fork the repository and create a new branch:

Expand Down
16 changes: 8 additions & 8 deletions examples/screenshot.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# /// script
# dependencies = ["fastmcp", "pyautogui", "Pillow"]
# ///

"""
FastMCP Screenshot Example
Give Claude a tool to capture and view screenshots.
"""

import io

from fastmcp import FastMCP, Image


# Create server
mcp = FastMCP("Screenshot Demo")
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"""
"""
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.
"""
import pyautogui

screenshot = pyautogui.screenshot()
buffer = io.BytesIO()

# if the file exceeds ~1MB, it will be rejected by Claude
screenshot = pyautogui.screenshot()
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
return Image(data=buffer.getvalue(), format="jpeg")
16 changes: 10 additions & 6 deletions src/fastmcp/cli/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,20 @@ def update_claude_config(
env_vars = existing_env

# Build uv run command
args = ["run", "--with", "fastmcp"]
args = ["run"]

# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
if with_packages:
packages.update(pkg for pkg in with_packages if pkg)

# Add all packages with --with
for pkg in sorted(packages):
args.extend(["--with", pkg])

if with_editable:
args.extend(["--with-editable", str(with_editable)])

if with_packages:
for pkg in with_packages:
if pkg:
args.extend(["--with", pkg])

# Convert file path to absolute before adding to command
# Split off any :object suffix first
if ":" in file_spec:
Expand Down
27 changes: 15 additions & 12 deletions src/fastmcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ def dev(
)

try:
# Import server to get dependencies
server = _import_server(file, server_object)
if hasattr(server, "dependencies"):
with_packages = list(set(with_packages + server.dependencies))

uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
# Run the MCP Inspector command
process = subprocess.run(
Expand Down Expand Up @@ -232,23 +237,16 @@ def run(
help="Transport protocol to use (stdio or sse)",
),
] = None,
with_editable: Annotated[
Optional[Path],
typer.Option(
"--with-editable",
"-e",
help="Directory containing pyproject.toml to install in editable mode",
exists=True,
file_okay=False,
resolve_path=True,
),
] = None,
) -> None:
"""Run a FastMCP server.
The server can be specified in two ways:
1. Module approach: server.py - runs the module directly, expecting a server.run() call
2. Import approach: server.py:app - imports and runs the specified server object
Note: This command runs the server directly. You are responsible for ensuring
all dependencies are available. For dependency management, use fastmcp install
or fastmcp dev instead.
"""
file, server_object = _parse_file_path(file_spec)

Expand All @@ -258,7 +256,6 @@ def run(
"file": str(file),
"server_object": server_object,
"transport": transport,
"with_editable": str(with_editable) if with_editable else None,
},
)

Expand Down Expand Up @@ -361,6 +358,7 @@ def install(

# Try to import server to get its name, but fall back to file name if dependencies missing
name = server_name
server = None
if not name:
try:
server = _import_server(file, server_object)
Expand All @@ -372,6 +370,11 @@ def install(
)
name = file.stem

# Get server dependencies if available
server_dependencies = getattr(server, "dependencies", []) if server else []
if server_dependencies:
with_packages = list(set(with_packages + server_dependencies))

# Process environment variables if provided
env_dict: Optional[Dict[str, str]] = None
if env_file or env_vars:
Expand Down
7 changes: 7 additions & 0 deletions src/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any, Callable, Dict, Literal, Sequence

import pydantic_core
from pydantic import Field
import uvicorn
from mcp.server import Server as MCPServer
from mcp.server.sse import SseServerTransport
Expand Down Expand Up @@ -76,6 +77,11 @@ class Settings(BaseSettings):
# prompt settings
warn_on_duplicate_prompts: bool = True

dependencies: list[str] = Field(
default_factory=list,
description="List of dependencies to install in the server environment",
)


class FastMCP:
def __init__(self, name: str | None = None, **settings: Any):
Expand All @@ -90,6 +96,7 @@ def __init__(self, name: str | None = None, **settings: Any):
self._prompt_manager = PromptManager(
warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts
)
self.dependencies = self.settings.dependencies

# Set up MCP protocol handlers
self._setup_handlers()
Expand Down
Loading

0 comments on commit 03cc8b1

Please sign in to comment.