From aa3c5e819285f41e07795f9771f21a8d50834c98 Mon Sep 17 00:00:00 2001 From: justjoehere Date: Mon, 2 Dec 2024 16:54:20 -0500 Subject: [PATCH 1/5] 1) Fixing this for windows in finding npm/npx for running servers in dev mode. 2) Fixing this for allowing server.py files to include module imports. Before they weren't resolving properly. --- src/fastmcp/cli/cli.py | 45 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py index 594caec3d..a73530300 100644 --- a/src/fastmcp/cli/cli.py +++ b/src/fastmcp/cli/cli.py @@ -11,8 +11,8 @@ import typer from typing_extensions import Annotated import dotenv -from ..utilities.logging import get_logger -from . import claude +from fastmcp.cli import claude +from fastmcp.utilities.logging import get_logger logger = get_logger("cli") @@ -24,6 +24,22 @@ app = typer.Typer( ) +def _get_npx_command(): + """Get the correct npx command for the current platform.""" + if sys.platform == "win32": + # Try both npx.cmd and npx.exe on Windows + for cmd in ["npx.cmd", "npx.exe", "npx"]: + try: + subprocess.run( + [cmd, "--version"], check=True, capture_output=True, shell=True + ) + return cmd + except subprocess.CalledProcessError: + continue + return None + 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.""" if "=" not in env_var: @@ -99,6 +115,11 @@ def _import_server(file: Path, server_object: Optional[str] = None): Returns: The server object """ + # Add parent directory to Python path so imports can be resolved + file_dir = str(file.parent) + if file_dir not in sys.path: + sys.path.insert(0, file_dir) + # Import the module spec = importlib.util.spec_from_file_location("server_module", file) if not spec or not spec.loader: @@ -205,10 +226,22 @@ def dev( with_packages = list(set(with_packages + server.dependencies)) uv_cmd = _build_uv_command(file_spec, with_editable, with_packages) - # Run the MCP Inspector command + + # Get the correct npx command + npx_cmd = _get_npx_command() + if not npx_cmd: + logger.error( + "npx not found. Please ensure Node.js and npm are properly installed " + "and added to your system PATH." + ) + sys.exit(1) + + # Run the MCP Inspector command with shell=True on Windows + shell = sys.platform == "win32" process = subprocess.run( - ["npx", "@modelcontextprotocol/inspector"] + uv_cmd, + [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd, check=True, + shell=shell, ) sys.exit(process.returncode) except subprocess.CalledProcessError as e: @@ -223,7 +256,9 @@ def dev( sys.exit(e.returncode) except FileNotFoundError: logger.error( - "npx not found. Please install Node.js and npm.", + "npx not found. Please ensure Node.js and npm are properly installed " + "and added to your system PATH. You may need to restart your terminal " + "after installation.", extra={"file": str(file)}, ) sys.exit(1) From f6172d03128f6ab1f4fb14c36ac34b079d31a90a Mon Sep 17 00:00:00 2001 From: justjoehere Date: Mon, 2 Dec 2024 16:57:40 -0500 Subject: [PATCH 2/5] Adding some notes for windows users --- Windows_Notes.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Windows_Notes.md b/Windows_Notes.md index 8993614af..1aaa39077 100644 --- a/Windows_Notes.md +++ b/Windows_Notes.md @@ -1,10 +1,14 @@ # 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 @@ -13,3 +17,6 @@ from collections.abc import Callable return isinstance(x, Callable) ``` +# Helpful notes + + From 72efb02b6dcc9df517ba0197ffb55c36f7998abb Mon Sep 17 00:00:00 2001 From: justjoehere Date: Mon, 2 Dec 2024 17:18:53 -0500 Subject: [PATCH 3/5] Adding some notes for windows users --- Windows_Notes.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Windows_Notes.md b/Windows_Notes.md index 1aaa39077..e3c08a3a2 100644 --- a/Windows_Notes.md +++ b/Windows_Notes.md @@ -18,5 +18,35 @@ 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. + + From f0a92df15515d160b48fdfa95bb8bafe4f9a0f36 Mon Sep 17 00:00:00 2001 From: justjoehere Date: Mon, 2 Dec 2024 17:22:46 -0500 Subject: [PATCH 4/5] Adding some notes for windows users --- Windows_Notes.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Windows_Notes.md b/Windows_Notes.md index e3c08a3a2..f2f9445eb 100644 --- a/Windows_Notes.md +++ b/Windows_Notes.md @@ -47,6 +47,12 @@ 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 ` +- Create your pull request on GitHub From 24a1f740955d6a2d11be1b0fe2e51582916f71c4 Mon Sep 17 00:00:00 2001 From: justjoehere Date: Mon, 2 Dec 2024 17:31:25 -0500 Subject: [PATCH 5/5] Made the test context aware. Since windows will called npx subprocess twice, once to find it, and once to execute it, but unix will only call once. --- tests/test_cli.py | 61 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 4ed0e1895..f81538148 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,6 +1,7 @@ """Tests for the FastMCP CLI.""" import json +import sys from pathlib import Path from unittest.mock import patch, call @@ -276,7 +277,6 @@ def test_server_dependencies_empty(mock_config, server_file): def test_dev_with_dependencies(mock_config, server_file): """Test that dev command handles dependencies correctly.""" - # Create a server file with dependencies server_file = server_file.parent / "server_with_deps.py" server_file.write_text( """from fastmcp import FastMCP @@ -287,21 +287,56 @@ mcp = FastMCP("test", dependencies=["pandas", "numpy"]) runner = CliRunner() with patch("subprocess.run") as mock_run: - mock_run.return_value.returncode = 0 # Set successful return code + mock_run.return_value.returncode = 0 result = runner.invoke(app, ["dev", str(server_file)]) assert result.exit_code == 0 - # Check that dependencies were passed to subprocess.run - mock_run.assert_called_once() - args = mock_run.call_args[0][0] - assert "npx" in args - assert "@modelcontextprotocol/inspector" in args - assert "uv" in args - assert "run" in args - assert "--with" in args - assert "pandas" in args - assert "numpy" in args - assert "fastmcp" in args + if sys.platform == "win32": + # On Windows, expect two calls + assert mock_run.call_count == 2 + assert mock_run.call_args_list[0] == call( + ["npx.cmd", "--version"], check=True, capture_output=True, shell=True + ) + assert mock_run.call_args_list[1] == call( + [ + "npx.cmd", + "@modelcontextprotocol/inspector", + "uv", + "run", + "--with", + "fastmcp", + "--with", + "numpy", + "--with", + "pandas", + "fastmcp", + "run", + str(server_file), + ], + check=True, + shell=True, + ) + else: + # On Unix, expect one call + mock_run.assert_called_once_with( + [ + "npx", + "@modelcontextprotocol/inspector", + "uv", + "run", + "--with", + "fastmcp", + "--with", + "numpy", + "--with", + "pandas", + "fastmcp", + "run", + str(server_file), + ], + check=True, + shell=False, # Note: shell=False on Unix + ) def test_run_with_dependencies(mock_config, server_file):