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.
- fastmcp-0.3.5/.github/ai-labeler.yml +88 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/ai-labeler.yml +1 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/run-tests.yml +12 -5
- {fastmcp-0.3.3 → fastmcp-0.3.5}/PKG-INFO +37 -16
- {fastmcp-0.3.3 → fastmcp-0.3.5}/README.md +30 -15
- fastmcp-0.3.5/Windows_Notes.md +58 -0
- fastmcp-0.3.5/examples/complex_inputs.py +28 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/pyproject.toml +3 -5
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/claude.py +19 -2
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/cli.py +47 -6
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/exceptions.py +4 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/base.py +12 -19
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/base.py +19 -15
- fastmcp-0.3.5/src/fastmcp/utilities/func_metadata.py +200 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_file_resources.py +8 -3
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_cli.py +86 -15
- fastmcp-0.3.5/tests/test_func_metadata.py +359 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_tool_manager.py +69 -1
- fastmcp-0.3.3/.github/ai-labeler.yml +0 -2
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/release.yml +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/lint.yml +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.github/workflows/publish.yml +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.gitignore +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.pre-commit-config.yaml +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/.python-version +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/LICENSE +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/docs/assets/demo-inspector.png +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/desktop.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/echo.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/readme-quickstart.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/screenshot.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/simple_echo.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/examples/text_me.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/cli/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/base.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/prompts/prompt_manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/resource_manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/templates.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/resources/types.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/server.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/tools/tool_manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/logging.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/src/fastmcp/utilities/types.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/test_base.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/prompts/test_manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_function_resources.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resource_manager.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resource_template.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/resources/test_resources.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/servers/__init__.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/servers/test_file_server.py +0 -0
- {fastmcp-0.3.3 → fastmcp-0.3.5}/tests/test_server.py +0 -0
- {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
|
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
+
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
521
|
+
git clone https://github.com/jlowin/fastmcp.git
|
|
498
522
|
cd fastmcp
|
|
523
|
+
uv sync --frozen --extra dev
|
|
499
524
|
```
|
|
500
525
|
|
|
501
|
-
|
|
526
|
+
For running tests only (e.g., in CI), you only need the testing dependencies:
|
|
502
527
|
|
|
503
528
|
```bash
|
|
504
|
-
uv
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
491
|
+
git clone https://github.com/jlowin/fastmcp.git
|
|
474
492
|
cd fastmcp
|
|
493
|
+
uv sync --frozen --extra dev
|
|
475
494
|
```
|
|
476
495
|
|
|
477
|
-
|
|
496
|
+
For running tests only (e.g., in CI), you only need the testing dependencies:
|
|
478
497
|
|
|
479
498
|
```bash
|
|
480
|
-
uv
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
from . import
|
|
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
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
|
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)
|
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
"""Base classes and interfaces for FastMCP resources."""
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from typing import
|
|
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:
|
|
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:
|