fastmcp 0.3.3__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.3 → fastmcp-0.3.5}/.github/workflows/ai-labeler.yml +1 -0
  3. {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/run-tests.yml +12 -5
  4. {fastmcp-0.3.3 → fastmcp-0.3.5}/PKG-INFO +37 -16
  5. {fastmcp-0.3.3 → fastmcp-0.3.5}/README.md +30 -15
  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.3 → fastmcp-0.3.5}/pyproject.toml +3 -5
  9. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/claude.py +19 -2
  10. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/cli.py +47 -6
  11. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/exceptions.py +4 -0
  12. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/base.py +12 -19
  13. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/base.py +19 -15
  14. fastmcp-0.3.5/src/fastmcp/utilities/func_metadata.py +200 -0
  15. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_file_resources.py +8 -3
  16. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_cli.py +86 -15
  17. fastmcp-0.3.5/tests/test_func_metadata.py +359 -0
  18. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_tool_manager.py +69 -1
  19. fastmcp-0.3.3/.github/ai-labeler.yml +0 -2
  20. {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/release.yml +0 -0
  21. {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/lint.yml +0 -0
  22. {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/publish.yml +0 -0
  23. {fastmcp-0.3.3 → fastmcp-0.3.5}/.gitignore +0 -0
  24. {fastmcp-0.3.3 → fastmcp-0.3.5}/.pre-commit-config.yaml +0 -0
  25. {fastmcp-0.3.3 → fastmcp-0.3.5}/.python-version +0 -0
  26. {fastmcp-0.3.3 → fastmcp-0.3.5}/LICENSE +0 -0
  27. {fastmcp-0.3.3 → fastmcp-0.3.5}/docs/assets/demo-inspector.png +0 -0
  28. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/desktop.py +0 -0
  29. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/echo.py +0 -0
  30. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/readme-quickstart.py +0 -0
  31. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/screenshot.py +0 -0
  32. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/simple_echo.py +0 -0
  33. {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/text_me.py +0 -0
  34. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/__init__.py +0 -0
  35. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/__init__.py +0 -0
  36. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/__init__.py +0 -0
  37. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/base.py +0 -0
  38. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/manager.py +0 -0
  39. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/prompt_manager.py +0 -0
  40. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/__init__.py +0 -0
  41. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/resource_manager.py +0 -0
  42. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/templates.py +0 -0
  43. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/types.py +0 -0
  44. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/server.py +0 -0
  45. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/__init__.py +0 -0
  46. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/tool_manager.py +0 -0
  47. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/__init__.py +0 -0
  48. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/logging.py +0 -0
  49. {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/types.py +0 -0
  50. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/__init__.py +0 -0
  51. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/__init__.py +0 -0
  52. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/test_base.py +0 -0
  53. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/test_manager.py +0 -0
  54. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/__init__.py +0 -0
  55. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_function_resources.py +0 -0
  56. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resource_manager.py +0 -0
  57. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resource_template.py +0 -0
  58. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resources.py +0 -0
  59. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/servers/__init__.py +0 -0
  60. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/servers/test_file_server.py +0 -0
  61. {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_server.py +0 -0
  62. {fastmcp-0.3.3 → 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.3
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 -->
@@ -122,6 +128,8 @@ pip install fastmcp
122
128
  Let's create a simple MCP server that exposes a calculator tool and some data:
123
129
 
124
130
  ```python
131
+ # server.py
132
+
125
133
  from fastmcp import FastMCP
126
134
 
127
135
 
@@ -143,14 +151,12 @@ def get_greeting(name: str) -> str:
143
151
  return f"Hello, {name}!"
144
152
  ```
145
153
 
146
- To use this server, you have two options:
147
-
148
- 1. Install it in [Claude Desktop](https://claude.ai/download):
154
+ You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running:
149
155
  ```bash
150
156
  fastmcp install server.py
151
157
  ```
152
158
 
153
- 2. Test it with the MCP Inspector:
159
+ Alternatively, you can test it with the MCP Inspector:
154
160
  ```bash
155
161
  fastmcp dev server.py
156
162
  ```
@@ -181,9 +187,6 @@ from fastmcp import FastMCP
181
187
  # Create a named server
182
188
  mcp = FastMCP("My App")
183
189
 
184
- # Configure host/port for HTTP transport (optional)
185
- mcp = FastMCP("My App", host="localhost", port=8000)
186
-
187
190
  # Specify dependencies for deployment and development
188
191
  mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
189
192
  ```
@@ -239,6 +242,27 @@ async def fetch_weather(city: str) -> str:
239
242
  return response.text
240
243
  ```
241
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
+
242
266
  ### Prompts
243
267
 
244
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:
@@ -491,23 +515,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
491
515
 
492
516
  ### Installation
493
517
 
494
- 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.
495
519
 
496
520
  ```bash
497
- git clone https://github.com/YouFancyUserYou/fastmcp.git
521
+ git clone https://github.com/jlowin/fastmcp.git
498
522
  cd fastmcp
523
+ uv sync --frozen --extra dev
499
524
  ```
500
525
 
501
- Next, create a virtual environment and install FastMCP:
526
+ For running tests only (e.g., in CI), you only need the testing dependencies:
502
527
 
503
528
  ```bash
504
- uv venv
505
- source .venv/bin/activate
506
- uv sync --frozen --all-extras --dev
529
+ uv sync --frozen --extra tests
507
530
  ```
508
531
 
509
-
510
-
511
532
  ### Testing
512
533
 
513
534
  Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
@@ -98,6 +98,8 @@ pip install fastmcp
98
98
  Let's create a simple MCP server that exposes a calculator tool and some data:
99
99
 
100
100
  ```python
101
+ # server.py
102
+
101
103
  from fastmcp import FastMCP
102
104
 
103
105
 
@@ -119,14 +121,12 @@ def get_greeting(name: str) -> str:
119
121
  return f"Hello, {name}!"
120
122
  ```
121
123
 
122
- To use this server, you have two options:
123
-
124
- 1. Install it in [Claude Desktop](https://claude.ai/download):
124
+ You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running:
125
125
  ```bash
126
126
  fastmcp install server.py
127
127
  ```
128
128
 
129
- 2. Test it with the MCP Inspector:
129
+ Alternatively, you can test it with the MCP Inspector:
130
130
  ```bash
131
131
  fastmcp dev server.py
132
132
  ```
@@ -157,9 +157,6 @@ from fastmcp import FastMCP
157
157
  # Create a named server
158
158
  mcp = FastMCP("My App")
159
159
 
160
- # Configure host/port for HTTP transport (optional)
161
- mcp = FastMCP("My App", host="localhost", port=8000)
162
-
163
160
  # Specify dependencies for deployment and development
164
161
  mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
165
162
  ```
@@ -215,6 +212,27 @@ async def fetch_weather(city: str) -> str:
215
212
  return response.text
216
213
  ```
217
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
+
218
236
  ### Prompts
219
237
 
220
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:
@@ -467,23 +485,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
467
485
 
468
486
  ### Installation
469
487
 
470
- 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.
471
489
 
472
490
  ```bash
473
- git clone https://github.com/YouFancyUserYou/fastmcp.git
491
+ git clone https://github.com/jlowin/fastmcp.git
474
492
  cd fastmcp
493
+ uv sync --frozen --extra dev
475
494
  ```
476
495
 
477
- Next, create a virtual environment and install FastMCP:
496
+ For running tests only (e.g., in CI), you only need the testing dependencies:
478
497
 
479
498
  ```bash
480
- uv venv
481
- source .venv/bin/activate
482
- uv sync --frozen --all-extras --dev
499
+ uv sync --frozen --extra tests
483
500
  ```
484
501
 
485
-
486
-
487
502
  ### Testing
488
503
 
489
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"
@@ -41,14 +41,31 @@ def update_claude_config(
41
41
  with_packages: Optional list of additional packages to install
42
42
  env_vars: Optional dictionary of environment variables. These are merged with
43
43
  any existing variables, with new values taking precedence.
44
+
45
+ Raises:
46
+ RuntimeError: If Claude Desktop's config directory is not found, indicating
47
+ Claude Desktop may not be installed or properly set up.
44
48
  """
45
49
  config_dir = get_claude_config_path()
46
50
  if not config_dir:
47
- return False
51
+ raise RuntimeError(
52
+ "Claude Desktop config directory not found. Please ensure Claude Desktop "
53
+ "is installed and has been run at least once to initialize its configuration."
54
+ )
48
55
 
49
56
  config_file = config_dir / "claude_desktop_config.json"
50
57
  if not config_file.exists():
51
- return False
58
+ try:
59
+ config_file.write_text("{}")
60
+ except Exception as e:
61
+ logger.error(
62
+ "Failed to create Claude config file",
63
+ extra={
64
+ "error": str(e),
65
+ "config_file": str(config_file),
66
+ },
67
+ )
68
+ return False
52
69
 
53
70
  try:
54
71
  config = json.loads(config_file.read_text())
@@ -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,34 +1,17 @@
1
1
  """Base classes and interfaces for FastMCP resources."""
2
2
 
3
3
  import abc
4
- from typing import Annotated, Union
4
+ from typing import Union
5
5
 
6
6
  from pydantic import (
7
7
  AnyUrl,
8
8
  BaseModel,
9
- BeforeValidator,
10
9
  ConfigDict,
11
10
  Field,
12
11
  FileUrl,
13
12
  ValidationInfo,
14
13
  field_validator,
15
14
  )
16
- from pydantic.networks import _BaseUrl # TODO: remove this once pydantic is updated
17
-
18
-
19
- def maybe_cast_str_to_any_url(x) -> AnyUrl:
20
- if isinstance(x, FileUrl):
21
- return x
22
- elif isinstance(x, AnyUrl):
23
- return x
24
- elif isinstance(x, str):
25
- if x.startswith("file://"):
26
- return FileUrl(x)
27
- return AnyUrl(x)
28
- raise ValueError(f"Expected str or AnyUrl, got {type(x)}")
29
-
30
-
31
- LaxAnyUrl = Annotated[_BaseUrl | str, BeforeValidator(maybe_cast_str_to_any_url)]
32
15
 
33
16
 
34
17
  class Resource(BaseModel, abc.ABC):
@@ -36,7 +19,8 @@ class Resource(BaseModel, abc.ABC):
36
19
 
37
20
  model_config = ConfigDict(validate_default=True)
38
21
 
39
- uri: LaxAnyUrl = Field(default=..., description="URI of the resource")
22
+ # uri: Annotated[AnyUrl, BeforeValidator(maybe_cast_str_to_any_url)] = Field(
23
+ uri: AnyUrl = Field(default=..., description="URI of the resource")
40
24
  name: str | None = Field(description="Name of the resource", default=None)
41
25
  description: str | None = Field(
42
26
  description="Description of the resource", default=None
@@ -47,6 +31,15 @@ class Resource(BaseModel, abc.ABC):
47
31
  pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
48
32
  )
49
33
 
34
+ @field_validator("uri", mode="before")
35
+ def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl:
36
+ if isinstance(uri, str):
37
+ # AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path")
38
+ if uri.startswith("file://"):
39
+ return FileUrl(uri)
40
+ return AnyUrl(uri)
41
+ return uri
42
+
50
43
  @field_validator("name", mode="before")
51
44
  @classmethod
52
45
  def set_default_name(cls, name: str | None, info: ValidationInfo) -> str: