fastmcp 2.2.0__tar.gz → 2.2.1__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 (144) hide show
  1. fastmcp-2.2.1/.github/ISSUE_TEMPLATE/bug.yml +63 -0
  2. fastmcp-2.2.1/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. fastmcp-2.2.1/.github/ISSUE_TEMPLATE/enhancement.yml +38 -0
  4. {fastmcp-2.2.0 → fastmcp-2.2.1}/.github/workflows/run-tests.yml +0 -1
  5. {fastmcp-2.2.0 → fastmcp-2.2.1}/.pre-commit-config.yaml +0 -1
  6. {fastmcp-2.2.0 → fastmcp-2.2.1}/PKG-INFO +9 -2
  7. {fastmcp-2.2.0 → fastmcp-2.2.1}/README.md +7 -0
  8. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/getting-started/quickstart.mdx +3 -3
  9. {fastmcp-2.2.0 → fastmcp-2.2.1}/pyproject.toml +1 -1
  10. fastmcp-2.2.1/src/contrib/README.md +9 -0
  11. fastmcp-2.2.1/src/contrib/bulk_tool_caller/README.md +35 -0
  12. fastmcp-2.2.1/src/contrib/bulk_tool_caller/__init__.py +3 -0
  13. fastmcp-2.2.1/src/contrib/bulk_tool_caller/bulk_tool_caller.py +131 -0
  14. fastmcp-2.2.1/src/contrib/bulk_tool_caller/example.py +17 -0
  15. fastmcp-2.2.1/src/contrib/mcp_mixin/README.md +39 -0
  16. fastmcp-2.2.1/src/contrib/mcp_mixin/__init__.py +8 -0
  17. fastmcp-2.2.1/src/contrib/mcp_mixin/example.py +52 -0
  18. fastmcp-2.2.1/src/contrib/mcp_mixin/mcp_mixin.py +208 -0
  19. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/server/server.py +3 -1
  20. fastmcp-2.2.1/tests/cli/test_run.py +22 -0
  21. fastmcp-2.2.1/tests/contrib/__init__.py +1 -0
  22. fastmcp-2.2.1/tests/contrib/test_bulk_tool_caller.py +221 -0
  23. fastmcp-2.2.1/tests/contrib/test_mcp_mixin.py +255 -0
  24. {fastmcp-2.2.0 → fastmcp-2.2.1}/uv.lock +5 -16
  25. fastmcp-2.2.0/tests/cli/test_run.py +0 -77
  26. {fastmcp-2.2.0 → fastmcp-2.2.1}/.cursor/rules/core-mcp-objects.mdc +0 -0
  27. {fastmcp-2.2.0 → fastmcp-2.2.1}/.github/release.yml +0 -0
  28. {fastmcp-2.2.0 → fastmcp-2.2.1}/.github/workflows/publish.yml +0 -0
  29. {fastmcp-2.2.0 → fastmcp-2.2.1}/.github/workflows/run-static.yml +0 -0
  30. {fastmcp-2.2.0 → fastmcp-2.2.1}/.gitignore +0 -0
  31. {fastmcp-2.2.0 → fastmcp-2.2.1}/LICENSE +0 -0
  32. {fastmcp-2.2.0 → fastmcp-2.2.1}/Windows_Notes.md +0 -0
  33. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/assets/demo-inspector.png +0 -0
  34. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/clients/client.mdx +0 -0
  35. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/clients/transports.mdx +0 -0
  36. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/docs.json +0 -0
  37. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/getting-started/installation.mdx +0 -0
  38. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/getting-started/welcome.mdx +0 -0
  39. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/patterns/composition.mdx +0 -0
  40. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/patterns/decorating-methods.mdx +0 -0
  41. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/patterns/fastapi.mdx +0 -0
  42. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/patterns/openapi.mdx +0 -0
  43. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/patterns/proxy.mdx +0 -0
  44. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/servers/context.mdx +0 -0
  45. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/servers/fastmcp.mdx +0 -0
  46. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/servers/prompts.mdx +0 -0
  47. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/servers/resources.mdx +0 -0
  48. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/servers/tools.mdx +0 -0
  49. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/snippets/version-badge.mdx +0 -0
  50. {fastmcp-2.2.0 → fastmcp-2.2.1}/docs/style.css +0 -0
  51. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/complex_inputs.py +0 -0
  52. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/desktop.py +0 -0
  53. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/echo.py +0 -0
  54. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/memory.py +0 -0
  55. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/mount_example.py +0 -0
  56. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/readme-quickstart.py +0 -0
  57. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/sampling.py +0 -0
  58. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/screenshot.py +0 -0
  59. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/simple_echo.py +0 -0
  60. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/README.md +0 -0
  61. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/pyproject.toml +0 -0
  62. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/__init__.py +0 -0
  63. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/__main__.py +0 -0
  64. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/hub.py +0 -0
  65. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/lights/__init__.py +0 -0
  66. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/lights/hue_utils.py +0 -0
  67. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/lights/server.py +0 -0
  68. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/py.typed +0 -0
  69. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/src/smart_home/settings.py +0 -0
  70. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/smart_home/uv.lock +0 -0
  71. {fastmcp-2.2.0 → fastmcp-2.2.1}/examples/text_me.py +0 -0
  72. {fastmcp-2.2.0 → fastmcp-2.2.1}/justfile +0 -0
  73. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/__init__.py +0 -0
  74. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/cli/__init__.py +0 -0
  75. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/cli/claude.py +0 -0
  76. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/cli/cli.py +0 -0
  77. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/__init__.py +0 -0
  78. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/base.py +0 -0
  79. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/client.py +0 -0
  80. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/roots.py +0 -0
  81. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/sampling.py +0 -0
  82. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/client/transports.py +0 -0
  83. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/exceptions.py +0 -0
  84. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/prompts/__init__.py +0 -0
  85. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/prompts/prompt.py +0 -0
  86. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/prompts/prompt_manager.py +0 -0
  87. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/py.typed +0 -0
  88. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/resources/__init__.py +0 -0
  89. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/resources/resource.py +0 -0
  90. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/resources/resource_manager.py +0 -0
  91. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/resources/template.py +0 -0
  92. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/resources/types.py +0 -0
  93. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/server/__init__.py +0 -0
  94. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/server/context.py +0 -0
  95. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/server/openapi.py +0 -0
  96. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/server/proxy.py +0 -0
  97. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/settings.py +0 -0
  98. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/tools/__init__.py +0 -0
  99. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/tools/tool.py +0 -0
  100. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/tools/tool_manager.py +0 -0
  101. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/__init__.py +0 -0
  102. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/decorators.py +0 -0
  103. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/func_metadata.py +0 -0
  104. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/logging.py +0 -0
  105. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/openapi.py +0 -0
  106. {fastmcp-2.2.0 → fastmcp-2.2.1}/src/fastmcp/utilities/types.py +0 -0
  107. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/__init__.py +0 -0
  108. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/client/__init__.py +0 -0
  109. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/client/test_client.py +0 -0
  110. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/client/test_roots.py +0 -0
  111. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/client/test_sampling.py +0 -0
  112. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/conftest.py +0 -0
  113. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/prompts/__init__.py +0 -0
  114. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/prompts/test_base.py +0 -0
  115. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/prompts/test_prompt_manager.py +0 -0
  116. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/__init__.py +0 -0
  117. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/test_file_resources.py +0 -0
  118. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/test_function_resources.py +0 -0
  119. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/test_resource_manager.py +0 -0
  120. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/test_resource_template.py +0 -0
  121. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/resources/test_resources.py +0 -0
  122. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/__init__.py +0 -0
  123. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_file_server.py +0 -0
  124. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_import_server.py +0 -0
  125. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_lifespan.py +0 -0
  126. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_mount.py +0 -0
  127. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_openapi.py +0 -0
  128. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_proxy.py +0 -0
  129. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_run_server.py +0 -0
  130. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/server/test_server.py +0 -0
  131. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/test_servers/fastmcp_server.py +0 -0
  132. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/test_servers/sse.py +0 -0
  133. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/test_servers/stdio.py +0 -0
  134. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/tools/__init__.py +0 -0
  135. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/tools/test_tool_manager.py +0 -0
  136. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/__init__.py +0 -0
  137. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/openapi/__init__.py +0 -0
  138. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/openapi/conftest.py +0 -0
  139. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/openapi/test_openapi.py +0 -0
  140. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/openapi/test_openapi_advanced.py +0 -0
  141. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/openapi/test_openapi_fastapi.py +0 -0
  142. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/test_decorated_function.py +0 -0
  143. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/test_func_metadata.py +0 -0
  144. {fastmcp-2.2.0 → fastmcp-2.2.1}/tests/utilities/test_logging.py +0 -0
@@ -0,0 +1,63 @@
1
+ name: 🐛 Bug Report
2
+ description: Report a bug or unexpected behavior in FastMCP
3
+ labels: [bug, pending]
4
+
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: Thank you for contributing to FastMCP! 🙏
9
+
10
+ - type: textarea
11
+ id: description
12
+ attributes:
13
+ label: Description
14
+ description: |
15
+ Please explain what you're experiencing and what you would expect to happen instead.
16
+
17
+ Provide as much detail as possible to help us understand and solve your problem quickly.
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: example
23
+ attributes:
24
+ label: Example Code
25
+ description: >
26
+ If applicable, please provide a self-contained,
27
+ [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)
28
+ demonstrating the bug.
29
+
30
+ placeholder: |
31
+ from fastmcp import FastMCP
32
+
33
+ ...
34
+ render: Python
35
+
36
+ - type: textarea
37
+ id: version
38
+ attributes:
39
+ label: Version Information
40
+ description: |
41
+ Please provide information about your FastMCP version, MCP version, Python version, and OS.
42
+
43
+ To get this information, run the following command in your terminal and paste the output below:
44
+
45
+ ```bash
46
+ fastmcp version
47
+ ```
48
+
49
+ If there is other information that would be helpful, please include it as well.
50
+ render: Text
51
+ validations:
52
+ required: true
53
+
54
+ - type: textarea
55
+ id: additional_context
56
+ attributes:
57
+ label: Additional Context
58
+ description: |
59
+ Add any other context about the problem here. This could include:
60
+ - The full error message and traceback (if applicable)
61
+ - Information about your environment (e.g., virtual environment, installed packages)
62
+ - Steps to reproduce the issue
63
+ - Any recent changes in your code or setup that might be relevant
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: FastMCP Documentation
4
+ url: https://gofastmcp.com
5
+ about: Please review the documentation before opening an issue.
6
+ - name: MCP Python SDK
7
+ url: https://github.com/modelcontextprotocol/python-sdk/issues
8
+ about: Issues related to the low-level MCP Python SDK, including the FastMCP 1.0 module that is included in the `mcp` package, should be filed on the official MCP repository.
@@ -0,0 +1,38 @@
1
+ name: 💡 Enhancement Request
2
+ description: Suggest an idea or improvement for FastMCP
3
+ labels: [enhancement, pending]
4
+
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: Thank you for contributing to FastMCP! We value your ideas for improving the framework. 💡
9
+
10
+ - type: textarea
11
+ id: description
12
+ attributes:
13
+ label: Enhancement Description
14
+ description: |
15
+ Please describe the enhancement you'd like to see in FastMCP.
16
+
17
+ - What problem would this solve?
18
+ - How would this improve your workflow or experience with FastMCP?
19
+ - Are there any alternative solutions you've considered?
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ id: use_case
25
+ attributes:
26
+ label: Use Case
27
+ description: |
28
+ Describe a specific use case or scenario where this enhancement would be beneficial.
29
+ If possible, provide an example of how you envision using this feature.
30
+
31
+ - type: textarea
32
+ id: example
33
+ attributes:
34
+ label: Proposed Implementation
35
+ description: >
36
+ If you have ideas about how this enhancement could be implemented,
37
+ please share them here. Code snippets, pseudocode, or general approaches are welcome.
38
+ render: Python
@@ -60,4 +60,3 @@ jobs:
60
60
 
61
61
  - name: Run tests
62
62
  run: uv run pytest -vv
63
- if: ${{ !(github.event.pull_request.head.repo.fork) }}
@@ -27,4 +27,3 @@ repos:
27
27
  hooks:
28
28
  - id: pyright-pretty
29
29
  files: ^src/|^tests/
30
- exclude: ^examples/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: The fast, Pythonic way to build MCP servers.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -17,11 +17,11 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
- Requires-Dist: dotenv>=0.9.9
21
20
  Requires-Dist: exceptiongroup>=1.2.2
22
21
  Requires-Dist: httpx>=0.28.1
23
22
  Requires-Dist: mcp<2.0.0,>=1.6.0
24
23
  Requires-Dist: openapi-pydantic>=0.5.1
24
+ Requires-Dist: python-dotenv>=1.1.0
25
25
  Requires-Dist: rich>=13.9.4
26
26
  Requires-Dist: typer>=0.15.2
27
27
  Requires-Dist: websockets>=15.0.1
@@ -95,6 +95,7 @@ FastMCP handles the complex protocol details and server management, letting you
95
95
  - [Proxy Servers](#proxy-servers)
96
96
  - [Composing MCP Servers](#composing-mcp-servers)
97
97
  - [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
98
+ - [Handling `stderr`](#handling-stderr)
98
99
  - [Running Your Server](#running-your-server)
99
100
  - [Development Mode (Recommended for Building \& Testing)](#development-mode-recommended-for-building--testing)
100
101
  - [Claude Desktop Integration (For Regular Use)](#claude-desktop-integration-for-regular-use)
@@ -695,6 +696,12 @@ mcp_server = FastMCP.from_openapi(openapi_spec, client=http_client)
695
696
  if __name__ == "__main__":
696
697
  mcp_server.run()
697
698
  ```
699
+
700
+ ### Handling `stderr`
701
+ The MCP spec allows for the server to write anything it wants to `stderr`, and it
702
+ doesn't specify the format in any way. FastMCP will forward the server's `stderr`
703
+ to the client's `stderr`.
704
+
698
705
  ## Running Your Server
699
706
 
700
707
  Choose the method that best suits your needs:
@@ -66,6 +66,7 @@ FastMCP handles the complex protocol details and server management, letting you
66
66
  - [Proxy Servers](#proxy-servers)
67
67
  - [Composing MCP Servers](#composing-mcp-servers)
68
68
  - [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
69
+ - [Handling `stderr`](#handling-stderr)
69
70
  - [Running Your Server](#running-your-server)
70
71
  - [Development Mode (Recommended for Building \& Testing)](#development-mode-recommended-for-building--testing)
71
72
  - [Claude Desktop Integration (For Regular Use)](#claude-desktop-integration-for-regular-use)
@@ -666,6 +667,12 @@ mcp_server = FastMCP.from_openapi(openapi_spec, client=http_client)
666
667
  if __name__ == "__main__":
667
668
  mcp_server.run()
668
669
  ```
670
+
671
+ ### Handling `stderr`
672
+ The MCP spec allows for the server to write anything it wants to `stderr`, and it
673
+ doesn't specify the format in any way. FastMCP will forward the server's `stderr`
674
+ to the client's `stderr`.
675
+
669
676
  ## Running Your Server
670
677
 
671
678
  Choose the method that best suits your needs:
@@ -32,7 +32,7 @@ from fastmcp import FastMCP
32
32
 
33
33
  mcp = FastMCP("My MCP Server")
34
34
 
35
- @mcp.tool
35
+ @mcp.tool()
36
36
  def greet(name: str) -> str:
37
37
  return f"Hello, {name}!"
38
38
  ```
@@ -48,7 +48,7 @@ from fastmcp import FastMCP, Client
48
48
 
49
49
  mcp = FastMCP("My MCP Server")
50
50
 
51
- @mcp.tool
51
+ @mcp.tool()
52
52
  def greet(name: str) -> str:
53
53
  return f"Hello, {name}!"
54
54
 
@@ -75,7 +75,7 @@ from fastmcp import FastMCP, Client
75
75
 
76
76
  mcp = FastMCP("My MCP Server")
77
77
 
78
- @mcp.tool
78
+ @mcp.tool()
79
79
  def greet(name: str) -> str:
80
80
  return f"Hello, {name}!"
81
81
 
@@ -4,7 +4,7 @@ dynamic = ["version"]
4
4
  description = "The fast, Pythonic way to build MCP servers."
5
5
  authors = [{ name = "Jeremiah Lowin" }]
6
6
  dependencies = [
7
- "dotenv>=0.9.9",
7
+ "python-dotenv>=1.1.0",
8
8
  "exceptiongroup>=1.2.2",
9
9
  "httpx>=0.28.1",
10
10
  "mcp>=1.6.0,<2.0.0",
@@ -0,0 +1,9 @@
1
+ # FastMCP Contrib Modules
2
+
3
+ This directory holds community-contributed modules for FastMCP. These modules extend FastMCP's functionality but are not officially maintained by the core team.
4
+
5
+ **Guarantees:**
6
+ * Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
7
+ * Changes to the core FastMCP library might break modules in `contrib` without explicit warnings in the main changelog.
8
+
9
+ Use these modules at your own discretion. Contributions are welcome, but please include tests and documentation.
@@ -0,0 +1,35 @@
1
+ # Bulk Tool Caller
2
+
3
+ This module provides the `BulkToolCaller` class, which extends the `MCPMixin` to offer tools for performing multiple tool calls in a single request to a FastMCP server. This can be useful for optimizing interactions with the server by reducing the overhead of individual tool calls.
4
+
5
+ ## Usage
6
+
7
+ To use the `BulkToolCaller`, see the example [example.py](./example.py) file. The `BulkToolCaller` can be instantiated and then registered with a FastMCP server URL. It provides methods to call multiple tools in bulk, either different tools or the same tool with different arguments.
8
+
9
+
10
+ ## Provided Tools
11
+
12
+ The `BulkToolCaller` provides the following tools:
13
+
14
+ ### `call_tools_bulk`
15
+
16
+ Calls multiple different tools registered on the MCP server in a single request.
17
+
18
+ - **Arguments:**
19
+ - `tool_calls` (list of `CallToolRequest`): A list of objects, where each object specifies the `tool` name and `arguments` for an individual tool call.
20
+ - `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.
21
+
22
+ - **Returns:**
23
+ A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.
24
+
25
+ ### `call_tool_bulk`
26
+
27
+ Calls a single tool registered on the MCP server multiple times with different arguments in a single request.
28
+
29
+ - **Arguments:**
30
+ - `tool` (str): The name of the tool to call.
31
+ - `tool_arguments` (list of dict): A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
32
+ - `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.
33
+
34
+ - **Returns:**
35
+ A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.
@@ -0,0 +1,3 @@
1
+ from .bulk_tool_caller import BulkToolCaller
2
+
3
+ __all__ = ["BulkToolCaller"]
@@ -0,0 +1,131 @@
1
+ from typing import Any
2
+
3
+ from mcp.types import CallToolResult
4
+ from pydantic import BaseModel, Field
5
+
6
+ from contrib.mcp_mixin.mcp_mixin import _DEFAULT_SEPARATOR_TOOL, MCPMixin, mcp_tool
7
+ from fastmcp import FastMCP
8
+ from fastmcp.client import Client
9
+ from fastmcp.client.transports import FastMCPTransport
10
+
11
+
12
+ class CallToolRequest(BaseModel):
13
+ """A class to represent a request to call a tool with specific arguments."""
14
+
15
+ tool: str = Field(description="The name of the tool to call.")
16
+ arguments: dict[str, Any] = Field(
17
+ description="A dictionary containing the arguments for the tool call."
18
+ )
19
+
20
+
21
+ class CallToolRequestResult(CallToolResult):
22
+ """
23
+ A class to represent the result of a bulk tool call.
24
+ It extends CallToolResult to include information about the requested tool call.
25
+ """
26
+
27
+ tool: str = Field(description="The name of the tool that was called.")
28
+ arguments: dict[str, Any] = Field(
29
+ description="The arguments used for the tool call."
30
+ )
31
+
32
+ @classmethod
33
+ def from_call_tool_result(
34
+ cls, result: CallToolResult, tool: str, arguments: dict[str, Any]
35
+ ) -> "CallToolRequestResult":
36
+ """
37
+ Create a CallToolRequestResult from a CallToolResult.
38
+ """
39
+ return cls(
40
+ tool=tool,
41
+ arguments=arguments,
42
+ isError=result.isError,
43
+ content=result.content,
44
+ )
45
+
46
+
47
+ class BulkToolCaller(MCPMixin):
48
+ """
49
+ A class to provide a "bulk tool call" tool for a FastMCP server
50
+ """
51
+
52
+ def register_tools(
53
+ self,
54
+ mcp_server: "FastMCP",
55
+ prefix: str | None = None,
56
+ separator: str = _DEFAULT_SEPARATOR_TOOL,
57
+ ) -> None:
58
+ """
59
+ Register the tools provided by this class with the given MCP server.
60
+ """
61
+ self.connection = FastMCPTransport(mcp_server)
62
+
63
+ super().register_tools(mcp_server=mcp_server)
64
+
65
+ @mcp_tool()
66
+ async def call_tools_bulk(
67
+ self, tool_calls: list[CallToolRequest], continue_on_error: bool = True
68
+ ) -> list[CallToolRequestResult]:
69
+ """
70
+ Call multiple tools registered on this MCP server in a single request. Each call can
71
+ be for a different tool and can include different arguments. Useful for speeding up
72
+ what would otherwise take several individual tool calls.
73
+ """
74
+ results = []
75
+
76
+ for tool_call in tool_calls:
77
+ result = await self._call_tool(tool_call.tool, tool_call.arguments)
78
+
79
+ results.append(result)
80
+
81
+ if result.isError and not continue_on_error:
82
+ return results
83
+
84
+ return results
85
+
86
+ @mcp_tool()
87
+ async def call_tool_bulk(
88
+ self,
89
+ tool: str,
90
+ tool_arguments: list[dict[str, str | int | float | bool | None]],
91
+ continue_on_error: bool = True,
92
+ ) -> list[CallToolRequestResult]:
93
+ """
94
+ Call a single tool registered on this MCP server multiple times with a single request.
95
+ Each call can include different arguments. Useful for speeding up what would otherwise
96
+ take several individual tool calls.
97
+
98
+ Args:
99
+ tool: The name of the tool to call.
100
+ tool_arguments: A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
101
+ """
102
+ results = []
103
+
104
+ for tool_call_arguments in tool_arguments:
105
+ result = await self._call_tool(tool, tool_call_arguments)
106
+
107
+ results.append(result)
108
+
109
+ if result.isError and not continue_on_error:
110
+ return results
111
+
112
+ return results
113
+
114
+ async def _call_tool(
115
+ self, tool: str, arguments: dict[str, Any]
116
+ ) -> CallToolRequestResult:
117
+ """
118
+ Helper method to call a tool with the provided arguments.
119
+ """
120
+
121
+ async with Client(self.connection) as client:
122
+ result = await client.call_tool(
123
+ name=tool, arguments=arguments, _return_raw_result=True
124
+ )
125
+
126
+ return CallToolRequestResult(
127
+ tool=tool,
128
+ arguments=arguments,
129
+ isError=result.isError,
130
+ content=result.content,
131
+ )
@@ -0,0 +1,17 @@
1
+ """Sample code for FastMCP using MCPMixin."""
2
+
3
+ from contrib.bulk_tool_caller import BulkToolCaller
4
+ from fastmcp import FastMCP
5
+
6
+ mcp = FastMCP()
7
+
8
+
9
+ @mcp.tool()
10
+ def echo_tool(text: str) -> str:
11
+ """Echo the input text"""
12
+ return text
13
+
14
+
15
+ bulk_tool_caller = BulkToolCaller()
16
+
17
+ bulk_tool_caller.register_tools(mcp)
@@ -0,0 +1,39 @@
1
+ # MCP Mixin
2
+
3
+ This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).
4
+
5
+ It allows developers to easily define classes whose methods can be registered as tools, resources, or prompts with a `FastMCP` server instance using the `register_all()`, `register_tools()`, `register_resources()`, or `register_prompts()` methods provided by the mixin.
6
+
7
+ ## Usage
8
+
9
+ Inherit from `MCPMixin` and use the decorators on the methods you want to register.
10
+
11
+ ```python
12
+ from fastmcp import FastMCP
13
+ from contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource
14
+
15
+ class MyComponent(MCPMixin):
16
+ @mcp_tool(name="my_tool", description="Does something cool.")
17
+ def tool_method(self):
18
+ return "Tool executed!"
19
+
20
+ @mcp_resource(uri="component://data")
21
+ def resource_method(self):
22
+ return {"data": "some data"}
23
+
24
+ mcp_server = FastMCP()
25
+ component = MyComponent()
26
+
27
+ # Register all decorated methods with a prefix
28
+ # Useful if you will have multiple instantiated objects of the same class
29
+ # and want to avoid name collisions.
30
+ component.register_all(mcp_server, prefix="my_comp")
31
+
32
+ # Register without a prefix
33
+ # component.register_all(mcp_server)
34
+
35
+ # Now 'my_comp_my_tool' tool and 'my_comp+component://data' resource are registered (if prefix used)
36
+ # Or 'my_tool' and 'component://data' are registered (if no prefix used)
37
+ ```
38
+
39
+ The `prefix` argument in registration methods is optional. If omitted, methods are registered with their original decorated names/URIs. Individual separators (`tools_separator`, `resources_separator`, `prompts_separator`) can also be provided to `register_all` to change the separator for specific types.
@@ -0,0 +1,8 @@
1
+ from .mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt
2
+
3
+ __all__ = [
4
+ "MCPMixin",
5
+ "mcp_tool",
6
+ "mcp_resource",
7
+ "mcp_prompt",
8
+ ]
@@ -0,0 +1,52 @@
1
+ """Sample code for FastMCP using MCPMixin."""
2
+
3
+ import asyncio
4
+
5
+ from contrib.mcp_mixin import (
6
+ MCPMixin,
7
+ mcp_prompt,
8
+ mcp_resource,
9
+ mcp_tool,
10
+ )
11
+ from fastmcp import FastMCP
12
+
13
+ mcp = FastMCP()
14
+
15
+
16
+ class Sample(MCPMixin):
17
+ def __init__(self, name):
18
+ self.name = name
19
+
20
+ @mcp_tool()
21
+ def first_tool(self):
22
+ """First tool description."""
23
+ return f"Executed tool {self.name}."
24
+
25
+ @mcp_resource(uri="test://test")
26
+ def first_resource(self):
27
+ """First resource description."""
28
+ return f"Executed resource {self.name}."
29
+
30
+ @mcp_prompt()
31
+ def first_prompt(self):
32
+ """First prompt description."""
33
+ return f"here's a prompt! {self.name}."
34
+
35
+
36
+ first_sample = Sample("First")
37
+ second_sample = Sample("Second")
38
+
39
+ first_sample.register_all(mcp_server=mcp, prefix="first")
40
+ second_sample.register_all(mcp_server=mcp, prefix="second")
41
+
42
+
43
+ async def list_components():
44
+ print("MCP Server running with registered components...")
45
+ print("Tools:", list(await mcp.get_tools()))
46
+ print("Resources:", list(await mcp.get_resources()))
47
+ print("Prompts:", list(await mcp.get_prompts()))
48
+
49
+
50
+ if __name__ == "__main__":
51
+ asyncio.run(list_components())
52
+ mcp.run()