Jeremiah Lowin commited on
Commit
3515dc3
·
unverified ·
2 Parent(s): 9c58e7f77b8c9c

Merge pull request #43 from justjoehere/windows2

Browse files

Additional Windows Fixes for Dev running and for importing modules in a server

Files changed (3) hide show
  1. Windows_Notes.md +43 -0
  2. src/fastmcp/cli/cli.py +40 -5
  3. tests/test_cli.py +48 -13
Windows_Notes.md CHANGED
@@ -1,10 +1,14 @@
1
  # Getting your development environment set up properly
 
2
  ```bash
3
  uv venv
4
  .venv\Scripts\activate
5
  uv pip install -e ".[dev]"
6
  ```
7
 
 
 
 
8
  # Fixing `AttributeError: module 'collections' has no attribute 'Callable'`
9
  - open `.venv\Lib\site-packages\pyreadline\py3k_compat.py`
10
  - change `return isinstance(x, collections.Callable)` to
@@ -13,3 +17,42 @@ from collections.abc import Callable
13
  return isinstance(x, Callable)
14
  ```
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Getting your development environment set up properly
2
+ To get your environment up and running properly, you'll need a slightly different set of commands that are windows specific:
3
  ```bash
4
  uv venv
5
  .venv\Scripts\activate
6
  uv pip install -e ".[dev]"
7
  ```
8
 
9
+ This will install the package in editable mode, and install the development dependencies.
10
+
11
+
12
  # Fixing `AttributeError: module 'collections' has no attribute 'Callable'`
13
  - open `.venv\Lib\site-packages\pyreadline\py3k_compat.py`
14
  - change `return isinstance(x, collections.Callable)` to
 
17
  return isinstance(x, Callable)
18
  ```
19
 
20
+ # Helpful notes
21
+ For developing FastMCP
22
+ ## Install local development version of FastMCP into a local FastMCP project server
23
+ - ensure
24
+ - change directories to your FastMCP Server location so you can install it in your .venv
25
+ - run `.venv\Scripts\activate` to activate your virtual environment
26
+ - Then run a series of commands to uninstall the old version and install the new
27
+ ```bash
28
+ # First uninstall
29
+ uv pip uninstall fastmcp
30
+
31
+ # Clean any build artifacts in your fastmcp directory
32
+ cd C:\path\to\fastmcp
33
+ del /s /q *.egg-info
34
+
35
+ # Then reinstall in your weather project
36
+ cd C:\path\to\new\fastmcp_server
37
+ uv pip install --no-cache-dir -e C:\Users\justj\PycharmProjects\fastmcp
38
+
39
+ # Check that it installed properly and has the correct git hash
40
+ pip show fastmcp
41
+ ```
42
+
43
+ ## Running the FastMCP server with Inspector
44
+ 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:
45
+ ```bash
46
+ fastmcp dev server.py
47
+ ```
48
+ This will launch a web app on http://localhost:5173/ that you can use to inspect the FastMCP server.
49
+
50
+ ## If you start development before creating a fork - your get out of jail free card
51
+ - Add your fork as a new remote to your local repository `git remote add fork git@github.com:YOUR-USERNAME/REPOSITORY-NAME.git`
52
+ - This will add your repo, short named 'fork', as a remote to your local repository
53
+ - Verify that it was added correctly by running `git remote -v`
54
+ - Commit your changes
55
+ - Push your changes to your fork `git push fork <branch>`
56
+ - Create your pull request on GitHub
57
+
58
+
src/fastmcp/cli/cli.py CHANGED
@@ -11,8 +11,8 @@ import typer
11
  from typing_extensions import Annotated
12
  import dotenv
13
 
14
- from ..utilities.logging import get_logger
15
- from . import claude
16
 
17
  logger = get_logger("cli")
18
 
@@ -24,6 +24,22 @@ app = typer.Typer(
24
  )
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def _parse_env_var(env_var: str) -> Tuple[str, str]:
28
  """Parse environment variable string in format KEY=VALUE."""
29
  if "=" not in env_var:
@@ -99,6 +115,11 @@ def _import_server(file: Path, server_object: Optional[str] = None):
99
  Returns:
100
  The server object
101
  """
 
 
 
 
 
102
  # Import the module
103
  spec = importlib.util.spec_from_file_location("server_module", file)
104
  if not spec or not spec.loader:
@@ -205,10 +226,22 @@ def dev(
205
  with_packages = list(set(with_packages + server.dependencies))
206
 
207
  uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
208
- # Run the MCP Inspector command
 
 
 
 
 
 
 
 
 
 
 
209
  process = subprocess.run(
210
- ["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
211
  check=True,
 
212
  )
213
  sys.exit(process.returncode)
214
  except subprocess.CalledProcessError as e:
@@ -223,7 +256,9 @@ def dev(
223
  sys.exit(e.returncode)
224
  except FileNotFoundError:
225
  logger.error(
226
- "npx not found. Please install Node.js and npm.",
 
 
227
  extra={"file": str(file)},
228
  )
229
  sys.exit(1)
 
11
  from typing_extensions import Annotated
12
  import dotenv
13
 
14
+ from fastmcp.cli import claude
15
+ from fastmcp.utilities.logging import get_logger
16
 
17
  logger = get_logger("cli")
18
 
 
24
  )
25
 
26
 
27
+ def _get_npx_command():
28
+ """Get the correct npx command for the current platform."""
29
+ if sys.platform == "win32":
30
+ # Try both npx.cmd and npx.exe on Windows
31
+ for cmd in ["npx.cmd", "npx.exe", "npx"]:
32
+ try:
33
+ subprocess.run(
34
+ [cmd, "--version"], check=True, capture_output=True, shell=True
35
+ )
36
+ return cmd
37
+ except subprocess.CalledProcessError:
38
+ continue
39
+ return None
40
+ return "npx" # On Unix-like systems, just use npx
41
+
42
+
43
  def _parse_env_var(env_var: str) -> Tuple[str, str]:
44
  """Parse environment variable string in format KEY=VALUE."""
45
  if "=" not in env_var:
 
115
  Returns:
116
  The server object
117
  """
118
+ # Add parent directory to Python path so imports can be resolved
119
+ file_dir = str(file.parent)
120
+ if file_dir not in sys.path:
121
+ sys.path.insert(0, file_dir)
122
+
123
  # Import the module
124
  spec = importlib.util.spec_from_file_location("server_module", file)
125
  if not spec or not spec.loader:
 
226
  with_packages = list(set(with_packages + server.dependencies))
227
 
228
  uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
229
+
230
+ # Get the correct npx command
231
+ npx_cmd = _get_npx_command()
232
+ if not npx_cmd:
233
+ logger.error(
234
+ "npx not found. Please ensure Node.js and npm are properly installed "
235
+ "and added to your system PATH."
236
+ )
237
+ sys.exit(1)
238
+
239
+ # Run the MCP Inspector command with shell=True on Windows
240
+ shell = sys.platform == "win32"
241
  process = subprocess.run(
242
+ [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
243
  check=True,
244
+ shell=shell,
245
  )
246
  sys.exit(process.returncode)
247
  except subprocess.CalledProcessError as e:
 
256
  sys.exit(e.returncode)
257
  except FileNotFoundError:
258
  logger.error(
259
+ "npx not found. Please ensure Node.js and npm are properly installed "
260
+ "and added to your system PATH. You may need to restart your terminal "
261
+ "after installation.",
262
  extra={"file": str(file)},
263
  )
264
  sys.exit(1)
tests/test_cli.py CHANGED
@@ -1,6 +1,7 @@
1
  """Tests for the FastMCP CLI."""
2
 
3
  import json
 
4
  from pathlib import Path
5
  from unittest.mock import patch, call
6
 
@@ -276,7 +277,6 @@ def test_server_dependencies_empty(mock_config, server_file):
276
 
277
  def test_dev_with_dependencies(mock_config, server_file):
278
  """Test that dev command handles dependencies correctly."""
279
- # Create a server file with dependencies
280
  server_file = server_file.parent / "server_with_deps.py"
281
  server_file.write_text(
282
  """from fastmcp import FastMCP
@@ -287,21 +287,56 @@ mcp = FastMCP("test", dependencies=["pandas", "numpy"])
287
  runner = CliRunner()
288
 
289
  with patch("subprocess.run") as mock_run:
290
- mock_run.return_value.returncode = 0 # Set successful return code
291
  result = runner.invoke(app, ["dev", str(server_file)])
292
  assert result.exit_code == 0
293
 
294
- # Check that dependencies were passed to subprocess.run
295
- mock_run.assert_called_once()
296
- args = mock_run.call_args[0][0]
297
- assert "npx" in args
298
- assert "@modelcontextprotocol/inspector" in args
299
- assert "uv" in args
300
- assert "run" in args
301
- assert "--with" in args
302
- assert "pandas" in args
303
- assert "numpy" in args
304
- assert "fastmcp" in args
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
 
307
  def test_run_with_dependencies(mock_config, server_file):
 
1
  """Tests for the FastMCP CLI."""
2
 
3
  import json
4
+ import sys
5
  from pathlib import Path
6
  from unittest.mock import patch, call
7
 
 
277
 
278
  def test_dev_with_dependencies(mock_config, server_file):
279
  """Test that dev command handles dependencies correctly."""
 
280
  server_file = server_file.parent / "server_with_deps.py"
281
  server_file.write_text(
282
  """from fastmcp import FastMCP
 
287
  runner = CliRunner()
288
 
289
  with patch("subprocess.run") as mock_run:
290
+ mock_run.return_value.returncode = 0
291
  result = runner.invoke(app, ["dev", str(server_file)])
292
  assert result.exit_code == 0
293
 
294
+ if sys.platform == "win32":
295
+ # On Windows, expect two calls
296
+ assert mock_run.call_count == 2
297
+ assert mock_run.call_args_list[0] == call(
298
+ ["npx.cmd", "--version"], check=True, capture_output=True, shell=True
299
+ )
300
+ assert mock_run.call_args_list[1] == call(
301
+ [
302
+ "npx.cmd",
303
+ "@modelcontextprotocol/inspector",
304
+ "uv",
305
+ "run",
306
+ "--with",
307
+ "fastmcp",
308
+ "--with",
309
+ "numpy",
310
+ "--with",
311
+ "pandas",
312
+ "fastmcp",
313
+ "run",
314
+ str(server_file),
315
+ ],
316
+ check=True,
317
+ shell=True,
318
+ )
319
+ else:
320
+ # On Unix, expect one call
321
+ mock_run.assert_called_once_with(
322
+ [
323
+ "npx",
324
+ "@modelcontextprotocol/inspector",
325
+ "uv",
326
+ "run",
327
+ "--with",
328
+ "fastmcp",
329
+ "--with",
330
+ "numpy",
331
+ "--with",
332
+ "pandas",
333
+ "fastmcp",
334
+ "run",
335
+ str(server_file),
336
+ ],
337
+ check=True,
338
+ shell=False, # Note: shell=False on Unix
339
+ )
340
 
341
 
342
  def test_run_with_dependencies(mock_config, server_file):