fastmcp 2.12.5__py3-none-any.whl → 2.13.2__py3-none-any.whl
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/__init__.py +2 -2
- fastmcp/cli/cli.py +11 -11
- fastmcp/cli/install/claude_code.py +6 -6
- fastmcp/cli/install/claude_desktop.py +3 -3
- fastmcp/cli/install/cursor.py +18 -12
- fastmcp/cli/install/gemini_cli.py +3 -3
- fastmcp/cli/install/mcp_json.py +3 -3
- fastmcp/cli/run.py +13 -8
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +115 -217
- fastmcp/client/client.py +105 -39
- fastmcp/client/logging.py +18 -14
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/transports.py +80 -25
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/component_manager/component_service.py +6 -6
- fastmcp/contrib/mcp_mixin/README.md +32 -1
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
- fastmcp/experimental/sampling/handlers/openai.py +2 -2
- fastmcp/experimental/server/openapi/__init__.py +5 -8
- fastmcp/experimental/server/openapi/components.py +11 -7
- fastmcp/experimental/server/openapi/routing.py +2 -2
- fastmcp/experimental/utilities/openapi/__init__.py +10 -15
- fastmcp/experimental/utilities/openapi/director.py +14 -15
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
- fastmcp/experimental/utilities/openapi/models.py +3 -3
- fastmcp/experimental/utilities/openapi/parser.py +37 -16
- fastmcp/experimental/utilities/openapi/schemas.py +2 -2
- fastmcp/mcp_config.py +3 -4
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +22 -19
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +14 -9
- fastmcp/resources/resource_manager.py +9 -168
- fastmcp/resources/template.py +107 -17
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +9 -5
- fastmcp/server/auth/auth.py +70 -43
- fastmcp/server/auth/handlers/authorize.py +326 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1510 -289
- fastmcp/server/auth/oidc_proxy.py +84 -20
- fastmcp/server/auth/providers/auth0.py +40 -21
- fastmcp/server/auth/providers/aws.py +29 -3
- fastmcp/server/auth/providers/azure.py +312 -131
- fastmcp/server/auth/providers/bearer.py +1 -1
- fastmcp/server/auth/providers/debug.py +114 -0
- fastmcp/server/auth/providers/descope.py +86 -29
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/github.py +29 -8
- fastmcp/server/auth/providers/google.py +48 -9
- fastmcp/server/auth/providers/in_memory.py +27 -3
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +48 -31
- fastmcp/server/auth/providers/oci.py +233 -0
- fastmcp/server/auth/providers/scalekit.py +238 -0
- fastmcp/server/auth/providers/supabase.py +188 -0
- fastmcp/server/auth/providers/workos.py +35 -17
- fastmcp/server/context.py +177 -51
- fastmcp/server/dependencies.py +39 -12
- fastmcp/server/elicitation.py +1 -1
- fastmcp/server/http.py +56 -17
- fastmcp/server/low_level.py +121 -2
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +476 -0
- fastmcp/server/middleware/error_handling.py +14 -10
- fastmcp/server/middleware/logging.py +50 -39
- fastmcp/server/middleware/middleware.py +29 -16
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi.py +10 -6
- fastmcp/server/proxy.py +22 -11
- fastmcp/server/server.py +725 -242
- fastmcp/settings.py +24 -10
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +70 -23
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +12 -10
- fastmcp/utilities/cli.py +67 -28
- fastmcp/utilities/components.py +7 -2
- fastmcp/utilities/inspect.py +79 -23
- fastmcp/utilities/json_schema.py +4 -4
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/logging.py +118 -8
- fastmcp/utilities/mcp_server_config/__init__.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +4 -4
- fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
- fastmcp/utilities/openapi.py +11 -11
- fastmcp/utilities/tests.py +85 -4
- fastmcp/utilities/types.py +78 -16
- fastmcp/utilities/ui.py +626 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/METADATA +22 -14
- fastmcp-2.13.2.dist-info/RECORD +144 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
- fastmcp/cli/claude.py +0 -135
- fastmcp/utilities/storage.py +0 -204
- fastmcp-2.12.5.dist-info/RECORD +0 -134
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.13.2
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers and clients.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -14,24 +14,27 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
19
|
Classifier: Typing :: Typed
|
|
19
20
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: authlib>=1.5
|
|
21
|
-
Requires-Dist: cyclopts>=
|
|
21
|
+
Requires-Dist: authlib>=1.6.5
|
|
22
|
+
Requires-Dist: cyclopts>=4.0.0
|
|
22
23
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
23
24
|
Requires-Dist: httpx>=0.28.1
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
25
|
+
Requires-Dist: jsonschema-path>=0.3.4
|
|
26
|
+
Requires-Dist: mcp!=1.21.1,<2.0.0,>=1.19.0
|
|
26
27
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
28
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
29
|
+
Requires-Dist: py-key-value-aio[disk,memory]<0.4.0,>=0.2.8
|
|
27
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
28
31
|
Requires-Dist: pyperclip>=1.9.0
|
|
29
32
|
Requires-Dist: python-dotenv>=1.1.0
|
|
30
33
|
Requires-Dist: rich>=13.9.4
|
|
34
|
+
Requires-Dist: uvicorn>=0.35
|
|
35
|
+
Requires-Dist: websockets>=15.0.1
|
|
31
36
|
Provides-Extra: openai
|
|
32
37
|
Requires-Dist: openai>=1.102.0; extra == 'openai'
|
|
33
|
-
Provides-Extra: websockets
|
|
34
|
-
Requires-Dist: websockets>=15.0.1; extra == 'websockets'
|
|
35
38
|
Description-Content-Type: text/markdown
|
|
36
39
|
|
|
37
40
|
<div align="center">
|
|
@@ -51,6 +54,7 @@ Description-Content-Type: text/markdown
|
|
|
51
54
|
*Made with ☕️ by [Prefect](https://www.prefect.io/)*
|
|
52
55
|
|
|
53
56
|
[](https://gofastmcp.com)
|
|
57
|
+
[](https://discord.gg/uu8dJCgttd)
|
|
54
58
|
[](https://pypi.org/project/fastmcp)
|
|
55
59
|
[](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
|
|
56
60
|
[](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
|
|
@@ -106,6 +110,8 @@ There are two ways to access the LLM-friendly documentation:
|
|
|
106
110
|
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
|
107
111
|
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
|
108
112
|
|
|
113
|
+
**Community:** Join our [Discord server](https://discord.gg/uu8dJCgttd) to connect with other FastMCP developers and share what you're building.
|
|
114
|
+
|
|
109
115
|
---
|
|
110
116
|
|
|
111
117
|
<!-- omit in toc -->
|
|
@@ -176,6 +182,8 @@ uv pip install fastmcp
|
|
|
176
182
|
|
|
177
183
|
For full installation instructions, including verification, upgrading from the official MCPSDK, and developer setup, see the [**Installation Guide**](https://gofastmcp.com/getting-started/installation).
|
|
178
184
|
|
|
185
|
+
**Dependency Licensing:** FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations. If this is a concern, you can install Cyclopts v5 alpha (`pip install "cyclopts>=5.0.0a1"`) which removes this dependency, or wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
|
|
186
|
+
|
|
179
187
|
## Core Concepts
|
|
180
188
|
|
|
181
189
|
These are the building blocks for creating MCP servers and clients with FastMCP.
|
|
@@ -244,7 +252,6 @@ Access MCP session capabilities within your tools, resources, or prompts by addi
|
|
|
244
252
|
|
|
245
253
|
- **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
|
|
246
254
|
- **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
|
|
247
|
-
- **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
|
|
248
255
|
- **Resource Access:** Use `ctx.read_resource()` to access resources on the server
|
|
249
256
|
- **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
|
|
250
257
|
- and more...
|
|
@@ -348,13 +355,14 @@ FastMCP provides comprehensive authentication support that sets it apart from ba
|
|
|
348
355
|
- **Auth0**
|
|
349
356
|
- **WorkOS**
|
|
350
357
|
- **Descope**
|
|
358
|
+
- **Discord**
|
|
351
359
|
- **JWT/Custom**
|
|
352
360
|
- **API Keys**
|
|
353
361
|
|
|
354
362
|
Protecting a server takes just two lines:
|
|
355
363
|
|
|
356
364
|
```python
|
|
357
|
-
from fastmcp.server.auth import GoogleProvider
|
|
365
|
+
from fastmcp.server.auth.providers.google import GoogleProvider
|
|
358
366
|
|
|
359
367
|
auth = GoogleProvider(client_id="...", client_secret="...", base_url="https://myserver.com")
|
|
360
368
|
mcp = FastMCP("Protected Server", auth=auth)
|
|
@@ -515,20 +523,20 @@ uv run pytest --cov=src --cov=examples --cov-report=html
|
|
|
515
523
|
|
|
516
524
|
### Static Checks
|
|
517
525
|
|
|
518
|
-
FastMCP uses `
|
|
526
|
+
FastMCP uses `prek` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
|
|
519
527
|
|
|
520
528
|
Install the hooks locally:
|
|
521
529
|
|
|
522
530
|
```bash
|
|
523
|
-
uv run
|
|
531
|
+
uv run prek install
|
|
524
532
|
```
|
|
525
533
|
|
|
526
534
|
The hooks will now run automatically on `git commit`. You can also run them manually at any time:
|
|
527
535
|
|
|
528
536
|
```bash
|
|
529
|
-
|
|
537
|
+
prek run --all-files
|
|
530
538
|
# or via uv
|
|
531
|
-
uv run
|
|
539
|
+
uv run prek run --all-files
|
|
532
540
|
```
|
|
533
541
|
|
|
534
542
|
### Pull Requests
|
|
@@ -536,7 +544,7 @@ uv run pre-commit run --all-files
|
|
|
536
544
|
1. Fork the repository on GitHub.
|
|
537
545
|
2. Create a feature branch from `main`.
|
|
538
546
|
3. Make your changes, including tests and documentation updates.
|
|
539
|
-
4. Ensure tests and
|
|
547
|
+
4. Ensure tests and prek hooks pass.
|
|
540
548
|
5. Commit your changes and push to your fork.
|
|
541
549
|
6. Open a pull request against the `main` branch of `jlowin/fastmcp`.
|
|
542
550
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
fastmcp/__init__.py,sha256=oKNRn6cUVk3nabmXR9SFEvKz-yotC8tVDNYRorxX-Wk,1544
|
|
2
|
+
fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
|
|
3
|
+
fastmcp/mcp_config.py,sha256=YXZ0piljrxFgPYEwYSwPw6IiPwU3Cwp2VzlT9CWxutc,11397
|
|
4
|
+
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
fastmcp/settings.py,sha256=RqtXzbIGFWLFbgK82pGOx-OGWtrVKZe3KcBWinCgACY,13936
|
|
6
|
+
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
7
|
+
fastmcp/cli/cli.py,sha256=FMrda7kHwNDCflUiJTSWD_w3vuuQsgSj2IuWk8ZZsEM,29108
|
|
8
|
+
fastmcp/cli/run.py,sha256=HeaiHYcVY17JpHg4UjnIHkP5ttU0PNd1bZIL3brif8A,7047
|
|
9
|
+
fastmcp/cli/install/__init__.py,sha256=FUrwjMVaxONgz1qO7suzJNz1xosRfR3TOHlr3Z77JXA,797
|
|
10
|
+
fastmcp/cli/install/claude_code.py,sha256=vGv8hbMUM6p5uQ1scy6E7Qxn0BZ2_INATF0xmSl5QWQ,7617
|
|
11
|
+
fastmcp/cli/install/claude_desktop.py,sha256=aX_BrH5ODEN6UPHdw-Gnh0r5g8TojvTA7trqQRCEdAw,6832
|
|
12
|
+
fastmcp/cli/install/cursor.py,sha256=0qSkKp4JuZj2dGOAsPph9XS_LswV8rQ8CqAuEx7TNhA,10685
|
|
13
|
+
fastmcp/cli/install/gemini_cli.py,sha256=G7NhKnH21893baQjmVbFpwRyMbYIq7bocPQz1CBUH_8,7630
|
|
14
|
+
fastmcp/cli/install/mcp_json.py,sha256=l7b0sWB10YlbcXtcwJv1X2iHEP9V9EwuuD63PyTMvXI,5832
|
|
15
|
+
fastmcp/cli/install/shared.py,sha256=_1MNGCqf7BsAL6ntwA75wn86-0g-248ppQSAPQ8uTXk,5103
|
|
16
|
+
fastmcp/client/__init__.py,sha256=QHvSGJCLejQkQ4o070vsUdKNB8vUhxckBByvHjnteTQ,663
|
|
17
|
+
fastmcp/client/client.py,sha256=_CiqszJLahHsNDgaOpkf5v8csJ6D1dDdpv3TiDsNwzs,38899
|
|
18
|
+
fastmcp/client/elicitation.py,sha256=VNWgeBe2KipLp9mCc-6AApmfYAU1OlH9_3JdskfW_Wc,2521
|
|
19
|
+
fastmcp/client/logging.py,sha256=WBByRoBIB-Bl3ZUJVFvHqRt4teYPAvqC8MnJ358Elg8,1939
|
|
20
|
+
fastmcp/client/messages.py,sha256=NIPjt-5js_DkI5BD4OVdTf6pz-nGjc2dtbgt-vAY234,4329
|
|
21
|
+
fastmcp/client/oauth_callback.py,sha256=3xqL5_HD1QS9eGfw31HzoVF94QQelq_0TTqS7qWDlQQ,7709
|
|
22
|
+
fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
|
|
23
|
+
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
24
|
+
fastmcp/client/sampling.py,sha256=MEXgywI46X-E78gCLCKEiQ14iu4uQqohLav-MNCtD_U,1819
|
|
25
|
+
fastmcp/client/transports.py,sha256=sYbh50h0qcgB5C6OLdzHSF1hn2IOfty3XJEu2Dlg42o,40993
|
|
26
|
+
fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
|
|
27
|
+
fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
|
|
28
|
+
fastmcp/client/auth/oauth.py,sha256=RJSazIALhLoNtqbJk1lsktYjVypJ89OtJE7_tjjQHgE,12445
|
|
29
|
+
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
30
|
+
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
31
|
+
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
32
|
+
fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py,sha256=2NcrGS59qvHo1lfbRaT8NSWfCxN66knciLxFvnGwCLY,4165
|
|
33
|
+
fastmcp/contrib/bulk_tool_caller/example.py,sha256=6og_8pCJN_CabworC5R82zPAwwwM-W7HNJLQQSnS3lU,319
|
|
34
|
+
fastmcp/contrib/component_manager/README.md,sha256=sTan1D51jzkPNnCQTxwd5JXGzWVy4DtkUjrUfNH3-F0,4457
|
|
35
|
+
fastmcp/contrib/component_manager/__init__.py,sha256=9xu58ftB0Aqd5RymZgnkJMH9rTHBcrO6iMQX9qdEy3s,164
|
|
36
|
+
fastmcp/contrib/component_manager/component_manager.py,sha256=lS2KDsx_W6GDncWx6NhVNhg1X0TeGwQHWqP5PzlDPRM,6424
|
|
37
|
+
fastmcp/contrib/component_manager/component_service.py,sha256=WGu3XcZSbHriSjvMfuroNZuT5xWWoYbEiIow8FPQNYg,8712
|
|
38
|
+
fastmcp/contrib/component_manager/example.py,sha256=N16OIHmQuR-LNEv7bkrv2rGdMs862Nc3AKKEPfw-6rU,1587
|
|
39
|
+
fastmcp/contrib/mcp_mixin/README.md,sha256=f6ine6z9kuEx1qKhY9jzrH6sAwj1oWpXcLXGvK0GMVk,5404
|
|
40
|
+
fastmcp/contrib/mcp_mixin/__init__.py,sha256=zZFHlAGexUJCDLAg4p7CGZDmb-mgPdN1xVv0tR4mg7I,153
|
|
41
|
+
fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
|
|
42
|
+
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=Ij009tiJBj1Lciz4abTICA1Il-kz_myr4aevQ4yGTNo,10582
|
|
43
|
+
fastmcp/experimental/sampling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
fastmcp/experimental/sampling/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
+
fastmcp/experimental/sampling/handlers/base.py,sha256=mCPFj9ETc-Ro38R_pzx9rHVM2_EADCecScMkNWd6Tbs,714
|
|
46
|
+
fastmcp/experimental/sampling/handlers/openai.py,sha256=ofNTv1p5Lf-A_SKWD-mEpx28k0E5SbYwRNLbjx-npyY,5765
|
|
47
|
+
fastmcp/experimental/server/openapi/README.md,sha256=1Mc1Ur15OxMn-wAPEa1rZIiNNSMdv9sboQ3YpvNpUXM,9886
|
|
48
|
+
fastmcp/experimental/server/openapi/__init__.py,sha256=cZPebMY9xwjW8nUgTN5MvawnZEFx9E0Oe_TFqSrevp0,728
|
|
49
|
+
fastmcp/experimental/server/openapi/components.py,sha256=rKbrIZRaxV-mtpmeW2ZlYtCLE2eaScZF-3rKaMi9-rA,13371
|
|
50
|
+
fastmcp/experimental/server/openapi/routing.py,sha256=hAhQCtode5NuEDVfzeMj5vK03T6ylOub_f7irgwoa5w,4031
|
|
51
|
+
fastmcp/experimental/server/openapi/server.py,sha256=WQeUA3v69ZhrzN1-cbrDTC8EsaOXHwDSQT_NPgYrECk,16099
|
|
52
|
+
fastmcp/experimental/utilities/openapi/README.md,sha256=pOXftamuVXxEMlOt-JAfpuvHeRGauC3l46ntD1WzM-A,8604
|
|
53
|
+
fastmcp/experimental/utilities/openapi/__init__.py,sha256=6FTQyP-kWvFg5Ykq53j7byBhPuysOyMYSrFTdUAKeO0,1592
|
|
54
|
+
fastmcp/experimental/utilities/openapi/director.py,sha256=bsK5W8-vdydbB85xMLy5WsQJewnObXaDrtAIS5HdKjY,7956
|
|
55
|
+
fastmcp/experimental/utilities/openapi/formatters.py,sha256=1RCd8DwPU8_4uF51pj8Qp3oSZkZmoxL5VUwxBzokAMg,15540
|
|
56
|
+
fastmcp/experimental/utilities/openapi/json_schema_converter.py,sha256=z8FjEDedsvAU1tT_ztl7oL_ERbjGufS3meVO-WKJhuE,13089
|
|
57
|
+
fastmcp/experimental/utilities/openapi/models.py,sha256=-kfndwZSe92tVtKAgOuFn5rk1tN7oydCZKtLOEMEalA,2805
|
|
58
|
+
fastmcp/experimental/utilities/openapi/parser.py,sha256=qsa68Ro1c8ov77kdEP20IwZqD74E4IGKjtfeIkn3HdE,34338
|
|
59
|
+
fastmcp/experimental/utilities/openapi/schemas.py,sha256=84nPtnOlfjNoFGDoVoWLs0dh_7Ps92p3AuHgpVA5a-s,23349
|
|
60
|
+
fastmcp/prompts/__init__.py,sha256=BQ5ooDJcNhb5maYBcg2mF1VaHAY_A64cEU3UiCQ3Lw8,179
|
|
61
|
+
fastmcp/prompts/prompt.py,sha256=G-P_Ln1xTQRy-bTyMFy2v--9guTNpn92e30cmysX1Q0,14335
|
|
62
|
+
fastmcp/prompts/prompt_manager.py,sha256=5ZyT0blp5owuaN5pz_TQsyH6zUGFoUiVTGfiEnqBuj8,4262
|
|
63
|
+
fastmcp/resources/__init__.py,sha256=si8aT_9taxUNN0vkfbifst_SCId56DZmYi4YOb4mtlE,463
|
|
64
|
+
fastmcp/resources/resource.py,sha256=Rt260JyWuC_7Ufo3TtKxjPyKCTV-3zfsFF9wqHL8yWw,7168
|
|
65
|
+
fastmcp/resources/resource_manager.py,sha256=R-dtlhCYHcH1bnGuD0QW5aRUo_12_NeLkn9VLp4xmmU,13308
|
|
66
|
+
fastmcp/resources/template.py,sha256=vu9InVUKc5CvEOUvlTXsZ8-tpet_-kf8yX-rNrxE4Pw,14802
|
|
67
|
+
fastmcp/resources/types.py,sha256=efFLGD1Xc5Xq3sxlPaZ_8gtJ2UOixueTBV4KQTi4cOU,4936
|
|
68
|
+
fastmcp/server/__init__.py,sha256=qxNmIJcqsrpxpUvCv0mhdEAaUn1UZd1xLd8XRoWUlfY,119
|
|
69
|
+
fastmcp/server/context.py,sha256=TYPUb7zr2rnNNmZSvaKbWy0J94dhdsL210eWl1HMPzQ,28650
|
|
70
|
+
fastmcp/server/dependencies.py,sha256=gbtI6vqAH-u-TV9tbXZUcYisMCyUHs_-Y_PXcFxx5w4,5292
|
|
71
|
+
fastmcp/server/elicitation.py,sha256=WYsj-H9U-t3b6awcLUWl1b1EA5X48Ef6_kvLhxYgYGs,8777
|
|
72
|
+
fastmcp/server/http.py,sha256=IMggGikJxIMg1CkHH1du3BiKtbD2SU4wDyS0xvLG1O8,12032
|
|
73
|
+
fastmcp/server/low_level.py,sha256=b1Sx0_Py0QxeLXSLdDA5PjR9Dd9ANB7KSNkkGSr1AeE,5490
|
|
74
|
+
fastmcp/server/openapi.py,sha256=xWiQC3mjk81G7ZWhXF3PpECuCM1arD2tiHF9EM9kUyU,42332
|
|
75
|
+
fastmcp/server/proxy.py,sha256=iGyBPPnhtzOxuTknv-OZEdzelgEF_MOBgI3RxEMxopI,26157
|
|
76
|
+
fastmcp/server/server.py,sha256=G9Qesj1_p0AYvnqTA89dun2p1nwYMl1O-PMksZb93k8,109753
|
|
77
|
+
fastmcp/server/auth/__init__.py,sha256=1VZ3MhZhlByvo7QCWT1tMIdRdMppUw4_TeqclSPJeiI,820
|
|
78
|
+
fastmcp/server/auth/auth.py,sha256=YjRM4zHvTlAfQYB97HEnXfCgOXA60Kcrb5O2nHsyka8,14524
|
|
79
|
+
fastmcp/server/auth/jwt_issuer.py,sha256=lJYvrpC1ygI4jkoJlL_nTH6m7FKdTw2lbEycKo4eHLY,7197
|
|
80
|
+
fastmcp/server/auth/middleware.py,sha256=xwj3fUCLSlJK6n1Ehp-FN1qnjKqEz8b7LGAGMTqQ8Hk,3284
|
|
81
|
+
fastmcp/server/auth/oauth_proxy.py,sha256=onInFJmGOxlAk2iZhug2AfncDFo3owkd1QbA6UQo_n0,92482
|
|
82
|
+
fastmcp/server/auth/oidc_proxy.py,sha256=1VsNrTeBNnVm_yO5JPoIig-XvAynnsnFBwhaIx4fywE,17414
|
|
83
|
+
fastmcp/server/auth/redirect_validation.py,sha256=Jlhela9xpTbw4aWnQ04A5Z-TW0HYOC3f9BMsq3NXx1Q,2000
|
|
84
|
+
fastmcp/server/auth/handlers/authorize.py,sha256=1zrmXqRUhjiWSHgUhfj0CcCkj3uSlGkTnxHzaic0xYs,11617
|
|
85
|
+
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
|
+
fastmcp/server/auth/providers/auth0.py,sha256=dZkc7hppii20YWota_6_Y3vdNw-DZSq0OyModbly-RA,7814
|
|
87
|
+
fastmcp/server/auth/providers/aws.py,sha256=MXoEEnXmeIlRjaHqTeNCmJ90iTx9jwUdEdpyLUmzfIc,10852
|
|
88
|
+
fastmcp/server/auth/providers/azure.py,sha256=Lq949keq4-AC7AR6Dbn5caEim5XOAK3WpnB-GmRPLtY,20891
|
|
89
|
+
fastmcp/server/auth/providers/bearer.py,sha256=LwkCfDJS48BxBxZwrIrauqNfCtDrJtGqYyEWnJjUq7s,923
|
|
90
|
+
fastmcp/server/auth/providers/debug.py,sha256=92erHZGQB1ATsl6PwrXui6h3WJ4wLxE9ACbI3JutmWY,3881
|
|
91
|
+
fastmcp/server/auth/providers/descope.py,sha256=y3PX3RmEL-JzHktKUbRW25QPZ7AVMGh579Pwgmr9P3k,9551
|
|
92
|
+
fastmcp/server/auth/providers/discord.py,sha256=AK7WRydWNnXAUWnFeYUqgcdqsGkwFfcKZcyZMuOQcUw,12616
|
|
93
|
+
fastmcp/server/auth/providers/github.py,sha256=xsv-Qj1VJRc64YcRuUG4a61xFH1nqqVX_biC7B1su9U,12414
|
|
94
|
+
fastmcp/server/auth/providers/google.py,sha256=BAw3XfB8fE2zr80OZUP-bZBnlHRmZQGuvmoVFgW5D1E,14723
|
|
95
|
+
fastmcp/server/auth/providers/in_memory.py,sha256=y8a8sfLQXctSB78yGpR3ZyG9x5pWRvI_t0LHgSZ4nvI,15444
|
|
96
|
+
fastmcp/server/auth/providers/introspection.py,sha256=v2hlcuxxwug5myCr4KcTZlawwazAWYVHuRb0d3er13w,10733
|
|
97
|
+
fastmcp/server/auth/providers/jwt.py,sha256=c-2Wji-CvuYt3U3unxjJR-5-EABRDks_755EpxKBDH8,20798
|
|
98
|
+
fastmcp/server/auth/providers/oci.py,sha256=-XXDCmxnyBYJ9kdv_Y3iLJ4MxLSOgUjZdJrGwH3vPrE,9849
|
|
99
|
+
fastmcp/server/auth/providers/scalekit.py,sha256=30J2HImUAkyknMgH7lUGytcDOy4d01ClxTrBCO4E3GQ,9064
|
|
100
|
+
fastmcp/server/auth/providers/supabase.py,sha256=9aK9fZ2OtccOF-ittMJnwj6sEzUNUTIrRPWAPLMwCac,7321
|
|
101
|
+
fastmcp/server/auth/providers/workos.py,sha256=_KWsgKPV4OJ6a37FaVgq2LIzM3Nx26G5QQhgS8x2MO4,17244
|
|
102
|
+
fastmcp/server/middleware/__init__.py,sha256=LXT2IcZI4gbAtR4TnA7v_1lOWBR6eaHiE3Cp32Pv0bc,155
|
|
103
|
+
fastmcp/server/middleware/caching.py,sha256=xYUXkFeuoLaIJ_TB2570qEBS1TtneJClJOpJGNsNbu8,18414
|
|
104
|
+
fastmcp/server/middleware/error_handling.py,sha256=eSMKrmIxDcnhzLGyOL49hup5k5e0iwvH_n2XVxJ69W8,7726
|
|
105
|
+
fastmcp/server/middleware/logging.py,sha256=Reta-f4z8suYkJn4rPyJWYrNBeU25w8Y40U0uaV9ygo,9427
|
|
106
|
+
fastmcp/server/middleware/middleware.py,sha256=qCzUFJ8vZfd2HwQFl8vcZ_waaaIpQEZgcBnNRahgRwk,6599
|
|
107
|
+
fastmcp/server/middleware/rate_limiting.py,sha256=MwhMOhgsIhZjYwEQB8H8961hohV5564JlTwwYy_9ctU,7915
|
|
108
|
+
fastmcp/server/middleware/timing.py,sha256=lL_xc-ErLD5lplfvd5-HIyWEbZhgNBYkcQ74KFXAMkA,5591
|
|
109
|
+
fastmcp/server/middleware/tool_injection.py,sha256=zElqBN-yjZvcTADp57e0dn86kpxT9xsFqvYztiXuA08,3595
|
|
110
|
+
fastmcp/server/sampling/handler.py,sha256=yjLzvxlGllE-EY4bc6djsijEmwMT24PCpV6vJl-sPcI,580
|
|
111
|
+
fastmcp/tools/__init__.py,sha256=XGcaMkBgwr-AHzbNjyjdb3ATgp5TQ0wzSq0nsrBD__E,201
|
|
112
|
+
fastmcp/tools/tool.py,sha256=stqAmfmtCLor_WCD9WpPE-a4dzvZX3wRP5imG97UvUU,22091
|
|
113
|
+
fastmcp/tools/tool_manager.py,sha256=pCQGvKimXYEigcUqRHBd6_mbfJwD2KN3i0SmFj9Fj_c,5913
|
|
114
|
+
fastmcp/tools/tool_transform.py,sha256=5ayCeLSUUTfhrWozODQ4yhF35t-XRnWPfc6vL3xtfJE,38579
|
|
115
|
+
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
116
|
+
fastmcp/utilities/auth.py,sha256=ZVHkNb4YBpLE1EmmFyhvFB2qfWDZdEYNH9TRI9jylOE,1140
|
|
117
|
+
fastmcp/utilities/cli.py,sha256=46gyOddE8kWhUV2lHFM7kA2v0YNyzcajvIX3Db8gJXk,12174
|
|
118
|
+
fastmcp/utilities/components.py,sha256=WY2Bmc4KOLFnDP3YVYB17q4tCCu8ky_naJ9MXFGX-6o,6009
|
|
119
|
+
fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
|
|
120
|
+
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
121
|
+
fastmcp/utilities/inspect.py,sha256=3wYUuQH1xCCCdzZwALHNqaRABH6iqpA43dIXEhqVb5Q,18030
|
|
122
|
+
fastmcp/utilities/json_schema.py,sha256=-XjtAVzCaaJ_S-HoWo7Aabvlu8ubBqyoOinm9E85F4o,8888
|
|
123
|
+
fastmcp/utilities/json_schema_type.py,sha256=AjBhZtAj9-g6goCZbzrSyFthlwrRrfrE4DzgCuphYdw,22250
|
|
124
|
+
fastmcp/utilities/logging.py,sha256=61wVk5yQ62km3K8kZtkKtT_3EN26VL85GYW0aMtnwKA,7175
|
|
125
|
+
fastmcp/utilities/mcp_config.py,sha256=qATTXMGiYET-7PflOixQOgiw3aOizX-RlloRjAo7nwI,1796
|
|
126
|
+
fastmcp/utilities/openapi.py,sha256=Q3DD3Yc3JwoUG0usSI75OxbLT1ASq9FQwOymx1F8YZk,63339
|
|
127
|
+
fastmcp/utilities/tests.py,sha256=ChjKv-k5vf9y4ZHqItagBtooqPNrQiiJLAARUVOEP6M,8922
|
|
128
|
+
fastmcp/utilities/types.py,sha256=GA6aweKOke8nJDnESka3EKv9vStfKK5NiCZwLUF8Ars,17231
|
|
129
|
+
fastmcp/utilities/ui.py,sha256=gcnha7Vj4xEBxdrS83EZlKpN_43AQzcgiZFEvkTqzqg,14252
|
|
130
|
+
fastmcp/utilities/mcp_server_config/__init__.py,sha256=hHBxEwRsrgN0Q-1bvj28X6UVGDpfG6dt3yfSBGsOY80,791
|
|
131
|
+
fastmcp/utilities/mcp_server_config/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
|
+
fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py,sha256=9XHryV-JnAcPP5YR_EzAkUDmj5Fm3PE8lNUgTflYZfs,15467
|
|
133
|
+
fastmcp/utilities/mcp_server_config/v1/schema.json,sha256=ymDNFOWzcpnhIMeJmVPTw9b-NtHoHoru8Mc0WlSVxUY,8602
|
|
134
|
+
fastmcp/utilities/mcp_server_config/v1/environments/__init__.py,sha256=Tkv0dmJ6tKKotOBo-tho09QVdvEjy37iBsvBbEwH0EA,256
|
|
135
|
+
fastmcp/utilities/mcp_server_config/v1/environments/base.py,sha256=fbC1C25jI1whwXLlIQtmji5B4UEHLgKvw5K8NICb33Y,826
|
|
136
|
+
fastmcp/utilities/mcp_server_config/v1/environments/uv.py,sha256=DPVAXM5JDTN89wOSQsFnww4khRfNphXY2yzVeiKicNg,9755
|
|
137
|
+
fastmcp/utilities/mcp_server_config/v1/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
138
|
+
fastmcp/utilities/mcp_server_config/v1/sources/base.py,sha256=Y5MCxJyoDsaxcBN1zDL0CZtF5oAXxT_yqQOI-ze9b34,967
|
|
139
|
+
fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py,sha256=eFX47XNXz2oKHW8MZvx60dqyHkBxdg2FMOrHcyAS28g,8106
|
|
140
|
+
fastmcp-2.13.2.dist-info/METADATA,sha256=h46scB8lCi_OgT7NX1NHa3YRNN8xL18PNFKGovrCavQ,20513
|
|
141
|
+
fastmcp-2.13.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
142
|
+
fastmcp-2.13.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
143
|
+
fastmcp-2.13.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
144
|
+
fastmcp-2.13.2.dist-info/RECORD,,
|
fastmcp/cli/claude.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"""Claude app integration utilities."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from fastmcp.utilities.logging import get_logger
|
|
10
|
-
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
|
11
|
-
|
|
12
|
-
logger = get_logger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def get_claude_config_path() -> Path | None:
|
|
16
|
-
"""Get the Claude config directory based on platform."""
|
|
17
|
-
if sys.platform == "win32":
|
|
18
|
-
path = Path(Path.home(), "AppData", "Roaming", "Claude")
|
|
19
|
-
elif sys.platform == "darwin":
|
|
20
|
-
path = Path(Path.home(), "Library", "Application Support", "Claude")
|
|
21
|
-
elif sys.platform.startswith("linux"):
|
|
22
|
-
path = Path(
|
|
23
|
-
os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
|
|
24
|
-
)
|
|
25
|
-
else:
|
|
26
|
-
return None
|
|
27
|
-
|
|
28
|
-
if path.exists():
|
|
29
|
-
return path
|
|
30
|
-
return None
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def update_claude_config(
|
|
34
|
-
file_spec: str,
|
|
35
|
-
server_name: str,
|
|
36
|
-
*,
|
|
37
|
-
with_editable: list[Path] | None = None,
|
|
38
|
-
with_packages: list[str] | None = None,
|
|
39
|
-
env_vars: dict[str, str] | None = None,
|
|
40
|
-
) -> bool:
|
|
41
|
-
"""Add or update a FastMCP server in Claude's configuration.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
file_spec: Path to the server file, optionally with :object suffix
|
|
45
|
-
server_name: Name for the server in Claude's config
|
|
46
|
-
with_editable: Optional list of directories to install in editable mode
|
|
47
|
-
with_packages: Optional list of additional packages to install
|
|
48
|
-
env_vars: Optional dictionary of environment variables. These are merged with
|
|
49
|
-
any existing variables, with new values taking precedence.
|
|
50
|
-
|
|
51
|
-
Raises:
|
|
52
|
-
RuntimeError: If Claude Desktop's config directory is not found, indicating
|
|
53
|
-
Claude Desktop may not be installed or properly set up.
|
|
54
|
-
"""
|
|
55
|
-
config_dir = get_claude_config_path()
|
|
56
|
-
if not config_dir:
|
|
57
|
-
raise RuntimeError(
|
|
58
|
-
"Claude Desktop config directory not found. Please ensure Claude Desktop"
|
|
59
|
-
" is installed and has been run at least once to initialize its config."
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
config_file = config_dir / "claude_desktop_config.json"
|
|
63
|
-
if not config_file.exists():
|
|
64
|
-
try:
|
|
65
|
-
config_file.write_text("{}")
|
|
66
|
-
except Exception as e:
|
|
67
|
-
logger.error(
|
|
68
|
-
"Failed to create Claude config file",
|
|
69
|
-
extra={
|
|
70
|
-
"error": str(e),
|
|
71
|
-
"config_file": str(config_file),
|
|
72
|
-
},
|
|
73
|
-
)
|
|
74
|
-
return False
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
config = json.loads(config_file.read_text())
|
|
78
|
-
if "mcpServers" not in config:
|
|
79
|
-
config["mcpServers"] = {}
|
|
80
|
-
|
|
81
|
-
# Always preserve existing env vars and merge with new ones
|
|
82
|
-
if (
|
|
83
|
-
server_name in config["mcpServers"]
|
|
84
|
-
and "env" in config["mcpServers"][server_name]
|
|
85
|
-
):
|
|
86
|
-
existing_env = config["mcpServers"][server_name]["env"]
|
|
87
|
-
if env_vars:
|
|
88
|
-
# New vars take precedence over existing ones
|
|
89
|
-
env_vars = {**existing_env, **env_vars}
|
|
90
|
-
else:
|
|
91
|
-
env_vars = existing_env
|
|
92
|
-
|
|
93
|
-
env_config = UVEnvironment(
|
|
94
|
-
dependencies=(with_packages or []) + ["fastmcp"],
|
|
95
|
-
editable=[str(p) for p in with_editable] if with_editable else None,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
# Convert file path to absolute before adding to command
|
|
99
|
-
# Split off any :object suffix first
|
|
100
|
-
if ":" in file_spec:
|
|
101
|
-
file_path, server_object = file_spec.rsplit(":", 1)
|
|
102
|
-
file_spec = f"{Path(file_path).resolve()}:{server_object}"
|
|
103
|
-
else:
|
|
104
|
-
file_spec = str(Path(file_spec).resolve())
|
|
105
|
-
|
|
106
|
-
# Build the full command
|
|
107
|
-
full_command = env_config.build_command(["fastmcp", "run", file_spec])
|
|
108
|
-
|
|
109
|
-
# Extract command and args for the config
|
|
110
|
-
server_config: dict[str, Any] = {
|
|
111
|
-
"command": full_command[0],
|
|
112
|
-
"args": full_command[1:],
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
# Add environment variables if specified
|
|
116
|
-
if env_vars:
|
|
117
|
-
server_config["env"] = env_vars
|
|
118
|
-
|
|
119
|
-
config["mcpServers"][server_name] = server_config
|
|
120
|
-
|
|
121
|
-
config_file.write_text(json.dumps(config, indent=2))
|
|
122
|
-
logger.info(
|
|
123
|
-
f"Added server '{server_name}' to Claude config",
|
|
124
|
-
extra={"config_file": str(config_file)},
|
|
125
|
-
)
|
|
126
|
-
return True
|
|
127
|
-
except Exception as e:
|
|
128
|
-
logger.error(
|
|
129
|
-
"Failed to update Claude config",
|
|
130
|
-
extra={
|
|
131
|
-
"error": str(e),
|
|
132
|
-
"config_file": str(config_file),
|
|
133
|
-
},
|
|
134
|
-
)
|
|
135
|
-
return False
|
fastmcp/utilities/storage.py
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
"""Key-value storage utilities for persistent data management."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any, Protocol
|
|
8
|
-
|
|
9
|
-
import pydantic_core
|
|
10
|
-
|
|
11
|
-
from fastmcp.utilities.logging import get_logger
|
|
12
|
-
|
|
13
|
-
logger = get_logger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class KVStorage(Protocol):
|
|
17
|
-
"""Protocol for key-value storage of JSON data."""
|
|
18
|
-
|
|
19
|
-
async def get(self, key: str) -> dict[str, Any] | None:
|
|
20
|
-
"""Get a JSON dict by key."""
|
|
21
|
-
...
|
|
22
|
-
|
|
23
|
-
async def set(self, key: str, value: dict[str, Any]) -> None:
|
|
24
|
-
"""Store a JSON dict by key."""
|
|
25
|
-
...
|
|
26
|
-
|
|
27
|
-
async def delete(self, key: str) -> None:
|
|
28
|
-
"""Delete a value by key."""
|
|
29
|
-
...
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class JSONFileStorage:
|
|
33
|
-
"""File-based key-value storage for JSON data with automatic metadata tracking.
|
|
34
|
-
|
|
35
|
-
Each key-value pair is stored as a separate JSON file on disk.
|
|
36
|
-
Keys are sanitized to be filesystem-safe.
|
|
37
|
-
|
|
38
|
-
The storage automatically wraps all data with metadata:
|
|
39
|
-
- timestamp: Timestamp when the entry was last written
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
cache_dir: Directory for storing JSON files
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self, cache_dir: Path):
|
|
46
|
-
"""Initialize JSON file storage."""
|
|
47
|
-
self.cache_dir = cache_dir
|
|
48
|
-
self.cache_dir.mkdir(exist_ok=True, parents=True)
|
|
49
|
-
|
|
50
|
-
def _get_safe_key(self, key: str) -> str:
|
|
51
|
-
"""Convert key to filesystem-safe string."""
|
|
52
|
-
safe_key = key
|
|
53
|
-
|
|
54
|
-
# Replace problematic characters with underscores
|
|
55
|
-
for char in [".", "/", "\\", ":", "*", "?", '"', "<", ">", "|", " "]:
|
|
56
|
-
safe_key = safe_key.replace(char, "_")
|
|
57
|
-
|
|
58
|
-
# Compress multiple underscores into one
|
|
59
|
-
while "__" in safe_key:
|
|
60
|
-
safe_key = safe_key.replace("__", "_")
|
|
61
|
-
|
|
62
|
-
# Strip leading and trailing underscores
|
|
63
|
-
safe_key = safe_key.strip("_")
|
|
64
|
-
|
|
65
|
-
return safe_key
|
|
66
|
-
|
|
67
|
-
def _get_file_path(self, key: str) -> Path:
|
|
68
|
-
"""Get the file path for a given key."""
|
|
69
|
-
safe_key = self._get_safe_key(key)
|
|
70
|
-
return self.cache_dir / f"{safe_key}.json"
|
|
71
|
-
|
|
72
|
-
async def get(self, key: str) -> dict[str, Any] | None:
|
|
73
|
-
"""Get a JSON dict from storage by key.
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
key: The key to retrieve
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
The stored dict or None if not found
|
|
80
|
-
"""
|
|
81
|
-
path = self._get_file_path(key)
|
|
82
|
-
try:
|
|
83
|
-
wrapper = json.loads(path.read_text())
|
|
84
|
-
|
|
85
|
-
# Expect wrapped format with metadata
|
|
86
|
-
if not isinstance(wrapper, dict) or "data" not in wrapper:
|
|
87
|
-
logger.warning(f"Invalid storage format for key '{key}'")
|
|
88
|
-
return None
|
|
89
|
-
|
|
90
|
-
logger.debug(f"Loaded data for key '{key}'")
|
|
91
|
-
return wrapper["data"]
|
|
92
|
-
|
|
93
|
-
except FileNotFoundError:
|
|
94
|
-
logger.debug(f"No data found for key '{key}'")
|
|
95
|
-
return None
|
|
96
|
-
except json.JSONDecodeError as e:
|
|
97
|
-
logger.warning(f"Failed to load data for key '{key}': {e}")
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
async def set(self, key: str, value: dict[str, Any]) -> None:
|
|
101
|
-
"""Store a JSON dict with metadata.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
key: The key to store under
|
|
105
|
-
value: The dict to store
|
|
106
|
-
"""
|
|
107
|
-
import time
|
|
108
|
-
|
|
109
|
-
path = self._get_file_path(key)
|
|
110
|
-
current_time = time.time()
|
|
111
|
-
|
|
112
|
-
# Create wrapper with metadata
|
|
113
|
-
wrapper = {
|
|
114
|
-
"data": value,
|
|
115
|
-
"timestamp": current_time,
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# Use pydantic_core for consistent JSON serialization
|
|
119
|
-
json_data = pydantic_core.to_json(wrapper, fallback=str)
|
|
120
|
-
path.write_bytes(json_data)
|
|
121
|
-
logger.debug(f"Saved data for key '{key}'")
|
|
122
|
-
|
|
123
|
-
async def delete(self, key: str) -> None:
|
|
124
|
-
"""Delete a value from storage.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
key: The key to delete
|
|
128
|
-
"""
|
|
129
|
-
path = self._get_file_path(key)
|
|
130
|
-
if path.exists():
|
|
131
|
-
path.unlink()
|
|
132
|
-
logger.debug(f"Deleted data for key '{key}'")
|
|
133
|
-
|
|
134
|
-
async def cleanup_old_entries(
|
|
135
|
-
self,
|
|
136
|
-
max_age_seconds: int = 30 * 24 * 60 * 60, # 30 days default
|
|
137
|
-
) -> int:
|
|
138
|
-
"""Remove entries older than the specified age.
|
|
139
|
-
|
|
140
|
-
Uses the timestamp field to determine age.
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
max_age_seconds: Maximum age in seconds (default 30 days)
|
|
144
|
-
|
|
145
|
-
Returns:
|
|
146
|
-
Number of entries removed
|
|
147
|
-
"""
|
|
148
|
-
import time
|
|
149
|
-
|
|
150
|
-
current_time = time.time()
|
|
151
|
-
removed_count = 0
|
|
152
|
-
|
|
153
|
-
for json_file in self.cache_dir.glob("*.json"):
|
|
154
|
-
try:
|
|
155
|
-
# Read the file and check timestamp
|
|
156
|
-
wrapper = json.loads(json_file.read_text())
|
|
157
|
-
|
|
158
|
-
# Check wrapped format
|
|
159
|
-
if not isinstance(wrapper, dict) or "data" not in wrapper:
|
|
160
|
-
continue # Invalid format, skip
|
|
161
|
-
|
|
162
|
-
if "timestamp" not in wrapper:
|
|
163
|
-
continue # No timestamp field, skip
|
|
164
|
-
|
|
165
|
-
entry_age = current_time - wrapper["timestamp"]
|
|
166
|
-
if entry_age > max_age_seconds:
|
|
167
|
-
json_file.unlink()
|
|
168
|
-
removed_count += 1
|
|
169
|
-
logger.debug(
|
|
170
|
-
f"Removed old entry '{json_file.stem}' (age: {entry_age:.0f}s)"
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
174
|
-
logger.debug(f"Error reading {json_file.name}: {e}")
|
|
175
|
-
continue
|
|
176
|
-
|
|
177
|
-
if removed_count > 0:
|
|
178
|
-
logger.info(f"Cleaned up {removed_count} old entries from storage")
|
|
179
|
-
|
|
180
|
-
return removed_count
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class InMemoryStorage:
|
|
184
|
-
"""In-memory key-value storage for JSON data.
|
|
185
|
-
|
|
186
|
-
Simple dict-based storage that doesn't persist across restarts.
|
|
187
|
-
Useful for testing or environments where file storage isn't available.
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
def __init__(self):
|
|
191
|
-
"""Initialize in-memory storage."""
|
|
192
|
-
self._data: dict[str, dict[str, Any]] = {}
|
|
193
|
-
|
|
194
|
-
async def get(self, key: str) -> dict[str, Any] | None:
|
|
195
|
-
"""Get a JSON dict from memory by key."""
|
|
196
|
-
return self._data.get(key)
|
|
197
|
-
|
|
198
|
-
async def set(self, key: str, value: dict[str, Any]) -> None:
|
|
199
|
-
"""Store a JSON dict in memory."""
|
|
200
|
-
self._data[key] = value
|
|
201
|
-
|
|
202
|
-
async def delete(self, key: str) -> None:
|
|
203
|
-
"""Delete a value from memory."""
|
|
204
|
-
self._data.pop(key, None)
|