fastmcp 0.3.4__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. fastmcp-0.3.5/.github/ai-labeler.yml +88 -0
  2. {fastmcp-0.3.4 → fastmcp-0.3.5}/.github/workflows/ai-labeler.yml +1 -0
  3. {fastmcp-0.3.4 → fastmcp-0.3.5}/.github/workflows/run-tests.yml +12 -5
  4. {fastmcp-0.3.4 → fastmcp-0.3.5}/PKG-INFO +33 -9
  5. {fastmcp-0.3.4 → fastmcp-0.3.5}/README.md +26 -8
  6. fastmcp-0.3.5/Windows_Notes.md +58 -0
  7. fastmcp-0.3.5/examples/complex_inputs.py +28 -0
  8. {fastmcp-0.3.4 → fastmcp-0.3.5}/pyproject.toml +3 -5
  9. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/cli/cli.py +47 -6
  10. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/exceptions.py +4 -0
  11. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/tools/base.py +19 -15
  12. fastmcp-0.3.5/src/fastmcp/utilities/func_metadata.py +200 -0
  13. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/test_file_resources.py +8 -3
  14. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/test_cli.py +86 -15
  15. fastmcp-0.3.5/tests/test_func_metadata.py +359 -0
  16. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/test_tool_manager.py +69 -1
  17. fastmcp-0.3.4/.github/ai-labeler.yml +0 -2
  18. {fastmcp-0.3.4 → fastmcp-0.3.5}/.github/release.yml +0 -0
  19. {fastmcp-0.3.4 → fastmcp-0.3.5}/.github/workflows/lint.yml +0 -0
  20. {fastmcp-0.3.4 → fastmcp-0.3.5}/.github/workflows/publish.yml +0 -0
  21. {fastmcp-0.3.4 → fastmcp-0.3.5}/.gitignore +0 -0
  22. {fastmcp-0.3.4 → fastmcp-0.3.5}/.pre-commit-config.yaml +0 -0
  23. {fastmcp-0.3.4 → fastmcp-0.3.5}/.python-version +0 -0
  24. {fastmcp-0.3.4 → fastmcp-0.3.5}/LICENSE +0 -0
  25. {fastmcp-0.3.4 → fastmcp-0.3.5}/docs/assets/demo-inspector.png +0 -0
  26. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/desktop.py +0 -0
  27. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/echo.py +0 -0
  28. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/readme-quickstart.py +0 -0
  29. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/screenshot.py +0 -0
  30. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/simple_echo.py +0 -0
  31. {fastmcp-0.3.4 → fastmcp-0.3.5}/examples/text_me.py +0 -0
  32. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/__init__.py +0 -0
  33. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/cli/__init__.py +0 -0
  34. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/cli/claude.py +0 -0
  35. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/prompts/__init__.py +0 -0
  36. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/prompts/base.py +0 -0
  37. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/prompts/manager.py +0 -0
  38. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/prompts/prompt_manager.py +0 -0
  39. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/resources/__init__.py +0 -0
  40. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/resources/base.py +0 -0
  41. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/resources/resource_manager.py +0 -0
  42. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/resources/templates.py +0 -0
  43. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/resources/types.py +0 -0
  44. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/server.py +0 -0
  45. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/tools/__init__.py +0 -0
  46. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/tools/tool_manager.py +0 -0
  47. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/utilities/__init__.py +0 -0
  48. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/utilities/logging.py +0 -0
  49. {fastmcp-0.3.4 → fastmcp-0.3.5}/src/fastmcp/utilities/types.py +0 -0
  50. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/__init__.py +0 -0
  51. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/prompts/__init__.py +0 -0
  52. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/prompts/test_base.py +0 -0
  53. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/prompts/test_manager.py +0 -0
  54. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/__init__.py +0 -0
  55. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/test_function_resources.py +0 -0
  56. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/test_resource_manager.py +0 -0
  57. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/test_resource_template.py +0 -0
  58. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/resources/test_resources.py +0 -0
  59. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/servers/__init__.py +0 -0
  60. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/servers/test_file_server.py +0 -0
  61. {fastmcp-0.3.4 → fastmcp-0.3.5}/tests/test_server.py +0 -0
  62. {fastmcp-0.3.4 → fastmcp-0.3.5}/uv.lock +0 -0
@@ -0,0 +1,88 @@
1
+ instructions: |
2
+ Apply the minimal set of labels that accurately characterize the issue/PR:
3
+ - Use at most 1-2 labels unless there's a compelling reason for more
4
+ - Prefer specific labels (bug, feature) over generic ones (question, help wanted)
5
+ - For PRs that fix bugs, use 'bug' not 'enhancement'
6
+ - Never combine: bug + enhancement, feature + enhancement. For these labels, only choose the most relevant one.
7
+ - Reserve 'question' and 'help wanted' for when they're the primary characteristic
8
+
9
+ labels:
10
+ - bug:
11
+ description: "Something isn't working as expected"
12
+ instructions: |
13
+ Apply when describing or fixing unexpected behavior:
14
+ - Issues: Clear error messages or unexpected outcomes
15
+ - PRs: Fixes for broken functionality
16
+ Don't apply enhancement/feature for bug fixes unless they add significant new functionality
17
+ beyond fixing the bug
18
+
19
+ - documentation:
20
+ description: "Improvements or additions to documentation"
21
+ instructions: |
22
+ Apply only when documentation is the primary focus:
23
+ - README updates
24
+ - Code comments and docstrings
25
+ - API documentation
26
+ - Usage examples
27
+ Don't apply for minor doc updates alongside code changes
28
+
29
+ - enhancement:
30
+ description: "Improvements to existing features"
31
+ instructions: |
32
+ Apply only for improvements to existing functionality:
33
+ - Performance improvements
34
+ - UI/UX improvements
35
+ - Expanded capabilities of existing features
36
+ Don't apply to:
37
+ - Bug fixes
38
+ - New features
39
+ - Minor tweaks
40
+
41
+ - feature:
42
+ description: "New functionality"
43
+ instructions: |
44
+ Apply only for net-new functionality:
45
+ - New API endpoints
46
+ - New commands or tools
47
+ - New user-facing capabilities
48
+ Don't apply to:
49
+ - Improvements to existing features (use enhancement)
50
+ - Bug fixes
51
+
52
+ - good first issue:
53
+ description: "Good for newcomers"
54
+ instructions: |
55
+ Apply very selectively to issues that are:
56
+ - Small in scope
57
+ - Well-documented
58
+ - Require minimal context
59
+ - Have clear success criteria
60
+ Don't apply if the task requires significant background knowledge
61
+
62
+ - help wanted:
63
+ description: "Extra attention is needed"
64
+ instructions: |
65
+ Apply only when it's the primary characteristic:
66
+ - Issue needs external expertise
67
+ - Current maintainers can't address it
68
+ - Additional contributors would be valuable
69
+ Don't apply just because an issue is open or needs work
70
+
71
+ - question:
72
+ description: "Further information is requested"
73
+ instructions: |
74
+ Apply only when the primary purpose is seeking information:
75
+ - Clarification needed before work can begin
76
+ - Architectural discussions
77
+ - Implementation strategy questions
78
+ Don't apply to:
79
+ - Bug reports that need more details
80
+ - Feature requests that need refinement
81
+
82
+ # These files will be included in the context if they exist
83
+ context-files:
84
+ - README.md
85
+ - CONTRIBUTING.md
86
+ - CODE_OF_CONDUCT.md
87
+ - .github/ISSUE_TEMPLATE/bug_report.md
88
+ - .github/ISSUE_TEMPLATE/feature_request.md
@@ -19,5 +19,6 @@ jobs:
19
19
  - uses: actions/checkout@v4
20
20
  - uses: jlowin/ai-labeler@v0.5.0
21
21
  with:
22
+ include-repo-labels: false
22
23
  openai-api-key: ${{ secrets.OPENAI_API_KEY }}
23
24
  controlflow-llm-model: openai/gpt-4o-mini
@@ -12,12 +12,14 @@ on:
12
12
  - "tests/**"
13
13
  - "uv.lock"
14
14
  - "pyproject.toml"
15
+ - ".github/workflows/**"
15
16
  pull_request:
16
17
  paths:
17
18
  - "src/**"
18
19
  - "tests/**"
19
20
  - "uv.lock"
20
21
  - "pyproject.toml"
22
+ - ".github/workflows/**"
21
23
 
22
24
  workflow_dispatch:
23
25
 
@@ -26,8 +28,13 @@ permissions:
26
28
 
27
29
  jobs:
28
30
  run_tests:
29
- name: Run tests
30
- runs-on: ubuntu-latest
31
+ name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
32
+ runs-on: ${{ matrix.os }}
33
+ strategy:
34
+ matrix:
35
+ os: [ubuntu-latest, windows-latest, macos-latest]
36
+ python-version: ["3.10"]
37
+ fail-fast: false
31
38
 
32
39
  steps:
33
40
  - uses: actions/checkout@v4
@@ -35,11 +42,11 @@ jobs:
35
42
  - name: Install uv
36
43
  uses: astral-sh/setup-uv@v4
37
44
 
38
- - name: Set up Python
39
- run: uv python install 3.11
45
+ - name: Set up Python ${{ matrix.python-version }}
46
+ run: uv python install ${{ matrix.python-version }}
40
47
 
41
48
  - name: Install FastMCP
42
- run: uv sync --extra dev
49
+ run: uv sync --extra tests
43
50
 
44
51
  - name: Run tests
45
52
  run: uv run pytest -vv
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fastmcp
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: A more ergonomic interface for MCP servers
5
5
  Author: Jeremiah Lowin
6
6
  License: Apache-2.0
@@ -20,6 +20,12 @@ Requires-Dist: pytest-asyncio>=0.23.5; extra == 'dev'
20
20
  Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
21
21
  Requires-Dist: pytest>=8.3.3; extra == 'dev'
22
22
  Requires-Dist: ruff; extra == 'dev'
23
+ Provides-Extra: tests
24
+ Requires-Dist: pre-commit; extra == 'tests'
25
+ Requires-Dist: pytest-asyncio>=0.23.5; extra == 'tests'
26
+ Requires-Dist: pytest-xdist>=3.6.1; extra == 'tests'
27
+ Requires-Dist: pytest>=8.3.3; extra == 'tests'
28
+ Requires-Dist: ruff; extra == 'tests'
23
29
  Description-Content-Type: text/markdown
24
30
 
25
31
  <!-- omit in toc -->
@@ -236,6 +242,27 @@ async def fetch_weather(city: str) -> str:
236
242
  return response.text
237
243
  ```
238
244
 
245
+ Complex input handling example:
246
+ ```python
247
+ from pydantic import BaseModel, Field
248
+ from typing import Annotated
249
+
250
+ class ShrimpTank(BaseModel):
251
+ class Shrimp(BaseModel):
252
+ name: Annotated[str, Field(max_length=10)]
253
+
254
+ shrimp: list[Shrimp]
255
+
256
+ @mcp.tool()
257
+ def name_shrimp(
258
+ tank: ShrimpTank,
259
+ # You can use pydantic Field in function signatures for validation.
260
+ extra_names: Annotated[list[str], Field(max_length=10)],
261
+ ) -> list[str]:
262
+ """List all shrimp names in the tank"""
263
+ return [shrimp.name for shrimp in tank.shrimp] + extra_names
264
+ ```
265
+
239
266
  ### Prompts
240
267
 
241
268
  Prompts are reusable templates that help LLMs interact with your server effectively. They're like "best practices" encoded into your server. A prompt can be as simple as a string:
@@ -488,23 +515,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
488
515
 
489
516
  ### Installation
490
517
 
491
- Create a fork of this repository, then clone it:
518
+ For development, we recommend installing FastMCP with development dependencies, which includes various utilities the maintainers find useful.
492
519
 
493
520
  ```bash
494
- git clone https://github.com/YouFancyUserYou/fastmcp.git
521
+ git clone https://github.com/jlowin/fastmcp.git
495
522
  cd fastmcp
523
+ uv sync --frozen --extra dev
496
524
  ```
497
525
 
498
- Next, create a virtual environment and install FastMCP:
526
+ For running tests only (e.g., in CI), you only need the testing dependencies:
499
527
 
500
528
  ```bash
501
- uv venv
502
- source .venv/bin/activate
503
- uv sync --frozen --all-extras --dev
529
+ uv sync --frozen --extra tests
504
530
  ```
505
531
 
506
-
507
-
508
532
  ### Testing
509
533
 
510
534
  Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
@@ -212,6 +212,27 @@ async def fetch_weather(city: str) -> str:
212
212
  return response.text
213
213
  ```
214
214
 
215
+ Complex input handling example:
216
+ ```python
217
+ from pydantic import BaseModel, Field
218
+ from typing import Annotated
219
+
220
+ class ShrimpTank(BaseModel):
221
+ class Shrimp(BaseModel):
222
+ name: Annotated[str, Field(max_length=10)]
223
+
224
+ shrimp: list[Shrimp]
225
+
226
+ @mcp.tool()
227
+ def name_shrimp(
228
+ tank: ShrimpTank,
229
+ # You can use pydantic Field in function signatures for validation.
230
+ extra_names: Annotated[list[str], Field(max_length=10)],
231
+ ) -> list[str]:
232
+ """List all shrimp names in the tank"""
233
+ return [shrimp.name for shrimp in tank.shrimp] + extra_names
234
+ ```
235
+
215
236
  ### Prompts
216
237
 
217
238
  Prompts are reusable templates that help LLMs interact with your server effectively. They're like "best practices" encoded into your server. A prompt can be as simple as a string:
@@ -464,23 +485,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
464
485
 
465
486
  ### Installation
466
487
 
467
- Create a fork of this repository, then clone it:
488
+ For development, we recommend installing FastMCP with development dependencies, which includes various utilities the maintainers find useful.
468
489
 
469
490
  ```bash
470
- git clone https://github.com/YouFancyUserYou/fastmcp.git
491
+ git clone https://github.com/jlowin/fastmcp.git
471
492
  cd fastmcp
493
+ uv sync --frozen --extra dev
472
494
  ```
473
495
 
474
- Next, create a virtual environment and install FastMCP:
496
+ For running tests only (e.g., in CI), you only need the testing dependencies:
475
497
 
476
498
  ```bash
477
- uv venv
478
- source .venv/bin/activate
479
- uv sync --frozen --all-extras --dev
499
+ uv sync --frozen --extra tests
480
500
  ```
481
501
 
482
-
483
-
484
502
  ### Testing
485
503
 
486
504
  Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
@@ -0,0 +1,58 @@
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
15
+ ```
16
+ from collections.abc import Callable
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
+
@@ -0,0 +1,28 @@
1
+ """
2
+ FastMCP Complex inputs Example
3
+
4
+ Demonstrates validation via pydantic with complex models.
5
+ """
6
+
7
+ from pydantic import BaseModel, Field
8
+ from typing import Annotated
9
+ from fastmcp.server import FastMCP
10
+
11
+ mcp = FastMCP("Shrimp Tank")
12
+
13
+
14
+ class ShrimpTank(BaseModel):
15
+ class Shrimp(BaseModel):
16
+ name: Annotated[str, Field(max_length=10)]
17
+
18
+ shrimp: list[Shrimp]
19
+
20
+
21
+ @mcp.tool()
22
+ def name_shrimp(
23
+ tank: ShrimpTank,
24
+ # You can use pydantic Field in function signatures for validation.
25
+ extra_names: Annotated[list[str], Field(max_length=10)],
26
+ ) -> list[str]:
27
+ """List all shrimp names in the tank"""
28
+ return [shrimp.name for shrimp in tank.shrimp] + extra_names
@@ -23,16 +23,14 @@ requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"]
23
23
  build-backend = "hatchling.build"
24
24
 
25
25
  [project.optional-dependencies]
26
- dev = [
27
- "copychat>=0.5.2",
28
- "ipython>=8.12.3",
29
- "pdbpp>=0.10.3",
26
+ tests = [
30
27
  "pre-commit",
31
- "pytest-xdist>=3.6.1",
32
28
  "pytest>=8.3.3",
33
29
  "pytest-asyncio>=0.23.5",
30
+ "pytest-xdist>=3.6.1",
34
31
  "ruff",
35
32
  ]
33
+ dev = ["fastmcp[tests]", "copychat>=0.5.2", "ipython>=8.12.3", "pdbpp>=0.10.3"]
36
34
 
37
35
  [tool.pytest.ini_options]
38
36
  asyncio_mode = "auto"
@@ -11,8 +11,8 @@ import typer
11
11
  from typing_extensions import Annotated
12
12
  import dotenv
13
13
 
14
- from ..utilities.logging import get_logger
15
- from . import claude
14
+ from fastmcp.cli import claude
15
+ from fastmcp.utilities.logging import get_logger
16
16
 
17
17
  logger = get_logger("cli")
18
18
 
@@ -24,6 +24,22 @@ app = typer.Typer(
24
24
  )
25
25
 
26
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
+
27
43
  def _parse_env_var(env_var: str) -> Tuple[str, str]:
28
44
  """Parse environment variable string in format KEY=VALUE."""
29
45
  if "=" not in env_var:
@@ -67,11 +83,17 @@ def _parse_file_path(file_spec: str) -> Tuple[Path, Optional[str]]:
67
83
  Returns:
68
84
  Tuple of (file_path, server_object)
69
85
  """
70
- if ":" in file_spec:
86
+ # First check if we have a Windows path (e.g., C:\...)
87
+ has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":"
88
+
89
+ # Split on the last colon, but only if it's not part of the Windows drive letter
90
+ # and there's actually another colon in the string after the drive letter
91
+ if ":" in (file_spec[2:] if has_windows_drive else file_spec):
71
92
  file_str, server_object = file_spec.rsplit(":", 1)
72
93
  else:
73
94
  file_str, server_object = file_spec, None
74
95
 
96
+ # Resolve the file path
75
97
  file_path = Path(file_str).expanduser().resolve()
76
98
  if not file_path.exists():
77
99
  logger.error(f"File not found: {file_path}")
@@ -93,6 +115,11 @@ def _import_server(file: Path, server_object: Optional[str] = None):
93
115
  Returns:
94
116
  The server object
95
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
+
96
123
  # Import the module
97
124
  spec = importlib.util.spec_from_file_location("server_module", file)
98
125
  if not spec or not spec.loader:
@@ -199,10 +226,22 @@ def dev(
199
226
  with_packages = list(set(with_packages + server.dependencies))
200
227
 
201
228
  uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
202
- # Run the MCP Inspector command
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"
203
241
  process = subprocess.run(
204
- ["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
242
+ [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
205
243
  check=True,
244
+ shell=shell,
206
245
  )
207
246
  sys.exit(process.returncode)
208
247
  except subprocess.CalledProcessError as e:
@@ -217,7 +256,9 @@ def dev(
217
256
  sys.exit(e.returncode)
218
257
  except FileNotFoundError:
219
258
  logger.error(
220
- "npx not found. Please install Node.js and npm.",
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.",
221
262
  extra={"file": str(file)},
222
263
  )
223
264
  sys.exit(1)
@@ -15,3 +15,7 @@ class ResourceError(FastMCPError):
15
15
 
16
16
  class ToolError(FastMCPError):
17
17
  """Error in tool operations."""
18
+
19
+
20
+ class InvalidSignature(Exception):
21
+ """Invalid signature for use with FastMCP."""
@@ -1,8 +1,8 @@
1
1
  import fastmcp
2
2
  from fastmcp.exceptions import ToolError
3
3
 
4
-
5
- from pydantic import BaseModel, Field, TypeAdapter, validate_call
4
+ from fastmcp.utilities.func_metadata import func_metadata, FuncMetadata
5
+ from pydantic import BaseModel, Field
6
6
 
7
7
 
8
8
  import inspect
@@ -19,6 +19,9 @@ class Tool(BaseModel):
19
19
  name: str = Field(description="Name of the tool")
20
20
  description: str = Field(description="Description of what the tool does")
21
21
  parameters: dict = Field(description="JSON schema for tool parameters")
22
+ fn_metadata: FuncMetadata = Field(
23
+ description="Metadata about the function including a pydantic model for tool arguments"
24
+ )
22
25
  is_async: bool = Field(description="Whether the tool is async")
23
26
  context_kwarg: Optional[str] = Field(
24
27
  None, description="Name of the kwarg that should receive context"
@@ -41,9 +44,6 @@ class Tool(BaseModel):
41
44
  func_doc = description or fn.__doc__ or ""
42
45
  is_async = inspect.iscoroutinefunction(fn)
43
46
 
44
- # Get schema from TypeAdapter - will fail if function isn't properly typed
45
- parameters = TypeAdapter(fn).json_schema()
46
-
47
47
  # Find context parameter if it exists
48
48
  if context_kwarg is None:
49
49
  sig = inspect.signature(fn)
@@ -52,14 +52,18 @@ class Tool(BaseModel):
52
52
  context_kwarg = param_name
53
53
  break
54
54
 
55
- # ensure the arguments are properly cast
56
- fn = validate_call(fn)
55
+ func_arg_metadata = func_metadata(
56
+ fn,
57
+ skip_names=[context_kwarg] if context_kwarg is not None else [],
58
+ )
59
+ parameters = func_arg_metadata.arg_model.model_json_schema()
57
60
 
58
61
  return cls(
59
62
  fn=fn,
60
63
  name=func_name,
61
64
  description=func_doc,
62
65
  parameters=parameters,
66
+ fn_metadata=func_arg_metadata,
63
67
  is_async=is_async,
64
68
  context_kwarg=context_kwarg,
65
69
  )
@@ -67,13 +71,13 @@ class Tool(BaseModel):
67
71
  async def run(self, arguments: dict, context: Optional["Context"] = None) -> Any:
68
72
  """Run the tool with arguments."""
69
73
  try:
70
- # Inject context if needed
71
- if self.context_kwarg:
72
- arguments[self.context_kwarg] = context
73
-
74
- # Call function with proper async handling
75
- if self.is_async:
76
- return await self.fn(**arguments)
77
- return self.fn(**arguments)
74
+ return await self.fn_metadata.call_fn_with_arg_validation(
75
+ self.fn,
76
+ self.is_async,
77
+ arguments,
78
+ {self.context_kwarg: context}
79
+ if self.context_kwarg is not None
80
+ else None,
81
+ )
78
82
  except Exception as e:
79
83
  raise ToolError(f"Error executing tool {self.name}: {e}") from e