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.
Files changed (108) hide show
  1. fastmcp/__init__.py +2 -2
  2. fastmcp/cli/cli.py +11 -11
  3. fastmcp/cli/install/claude_code.py +6 -6
  4. fastmcp/cli/install/claude_desktop.py +3 -3
  5. fastmcp/cli/install/cursor.py +18 -12
  6. fastmcp/cli/install/gemini_cli.py +3 -3
  7. fastmcp/cli/install/mcp_json.py +3 -3
  8. fastmcp/cli/run.py +13 -8
  9. fastmcp/client/__init__.py +9 -9
  10. fastmcp/client/auth/oauth.py +115 -217
  11. fastmcp/client/client.py +105 -39
  12. fastmcp/client/logging.py +18 -14
  13. fastmcp/client/oauth_callback.py +85 -171
  14. fastmcp/client/sampling.py +1 -1
  15. fastmcp/client/transports.py +80 -25
  16. fastmcp/contrib/component_manager/__init__.py +1 -1
  17. fastmcp/contrib/component_manager/component_manager.py +2 -2
  18. fastmcp/contrib/component_manager/component_service.py +6 -6
  19. fastmcp/contrib/mcp_mixin/README.md +32 -1
  20. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  21. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  22. fastmcp/experimental/sampling/handlers/openai.py +2 -2
  23. fastmcp/experimental/server/openapi/__init__.py +5 -8
  24. fastmcp/experimental/server/openapi/components.py +11 -7
  25. fastmcp/experimental/server/openapi/routing.py +2 -2
  26. fastmcp/experimental/utilities/openapi/__init__.py +10 -15
  27. fastmcp/experimental/utilities/openapi/director.py +14 -15
  28. fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
  29. fastmcp/experimental/utilities/openapi/models.py +3 -3
  30. fastmcp/experimental/utilities/openapi/parser.py +37 -16
  31. fastmcp/experimental/utilities/openapi/schemas.py +2 -2
  32. fastmcp/mcp_config.py +3 -4
  33. fastmcp/prompts/__init__.py +1 -1
  34. fastmcp/prompts/prompt.py +22 -19
  35. fastmcp/prompts/prompt_manager.py +16 -101
  36. fastmcp/resources/__init__.py +5 -5
  37. fastmcp/resources/resource.py +14 -9
  38. fastmcp/resources/resource_manager.py +9 -168
  39. fastmcp/resources/template.py +107 -17
  40. fastmcp/resources/types.py +30 -24
  41. fastmcp/server/__init__.py +1 -1
  42. fastmcp/server/auth/__init__.py +9 -5
  43. fastmcp/server/auth/auth.py +70 -43
  44. fastmcp/server/auth/handlers/authorize.py +326 -0
  45. fastmcp/server/auth/jwt_issuer.py +236 -0
  46. fastmcp/server/auth/middleware.py +96 -0
  47. fastmcp/server/auth/oauth_proxy.py +1510 -289
  48. fastmcp/server/auth/oidc_proxy.py +84 -20
  49. fastmcp/server/auth/providers/auth0.py +40 -21
  50. fastmcp/server/auth/providers/aws.py +29 -3
  51. fastmcp/server/auth/providers/azure.py +312 -131
  52. fastmcp/server/auth/providers/bearer.py +1 -1
  53. fastmcp/server/auth/providers/debug.py +114 -0
  54. fastmcp/server/auth/providers/descope.py +86 -29
  55. fastmcp/server/auth/providers/discord.py +308 -0
  56. fastmcp/server/auth/providers/github.py +29 -8
  57. fastmcp/server/auth/providers/google.py +48 -9
  58. fastmcp/server/auth/providers/in_memory.py +27 -3
  59. fastmcp/server/auth/providers/introspection.py +281 -0
  60. fastmcp/server/auth/providers/jwt.py +48 -31
  61. fastmcp/server/auth/providers/oci.py +233 -0
  62. fastmcp/server/auth/providers/scalekit.py +238 -0
  63. fastmcp/server/auth/providers/supabase.py +188 -0
  64. fastmcp/server/auth/providers/workos.py +35 -17
  65. fastmcp/server/context.py +177 -51
  66. fastmcp/server/dependencies.py +39 -12
  67. fastmcp/server/elicitation.py +1 -1
  68. fastmcp/server/http.py +56 -17
  69. fastmcp/server/low_level.py +121 -2
  70. fastmcp/server/middleware/__init__.py +1 -1
  71. fastmcp/server/middleware/caching.py +476 -0
  72. fastmcp/server/middleware/error_handling.py +14 -10
  73. fastmcp/server/middleware/logging.py +50 -39
  74. fastmcp/server/middleware/middleware.py +29 -16
  75. fastmcp/server/middleware/rate_limiting.py +3 -3
  76. fastmcp/server/middleware/tool_injection.py +116 -0
  77. fastmcp/server/openapi.py +10 -6
  78. fastmcp/server/proxy.py +22 -11
  79. fastmcp/server/server.py +725 -242
  80. fastmcp/settings.py +24 -10
  81. fastmcp/tools/__init__.py +1 -1
  82. fastmcp/tools/tool.py +70 -23
  83. fastmcp/tools/tool_manager.py +30 -112
  84. fastmcp/tools/tool_transform.py +12 -10
  85. fastmcp/utilities/cli.py +67 -28
  86. fastmcp/utilities/components.py +7 -2
  87. fastmcp/utilities/inspect.py +79 -23
  88. fastmcp/utilities/json_schema.py +4 -4
  89. fastmcp/utilities/json_schema_type.py +4 -4
  90. fastmcp/utilities/logging.py +118 -8
  91. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  92. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  93. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  94. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +4 -4
  95. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  96. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  97. fastmcp/utilities/openapi.py +11 -11
  98. fastmcp/utilities/tests.py +85 -4
  99. fastmcp/utilities/types.py +78 -16
  100. fastmcp/utilities/ui.py +626 -0
  101. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/METADATA +22 -14
  102. fastmcp-2.13.2.dist-info/RECORD +144 -0
  103. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
  104. fastmcp/cli/claude.py +0 -135
  105. fastmcp/utilities/storage.py +0 -204
  106. fastmcp-2.12.5.dist-info/RECORD +0 -134
  107. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
  108. {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.12.5
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.2
21
- Requires-Dist: cyclopts>=3.0.0
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: mcp<1.17.0,>=1.12.4
25
- Requires-Dist: openapi-core>=0.19.5
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
  [![Docs](https://img.shields.io/badge/docs-gofastmcp.com-blue)](https://gofastmcp.com)
57
+ [![Discord](https://img.shields.io/badge/community-discord-5865F2?logo=discord&logoColor=white)](https://discord.gg/uu8dJCgttd)
54
58
  [![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
55
59
  [![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
56
60
  [![License](https://img.shields.io/github/license/jlowin/fastmcp.svg)](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 `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
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 pre-commit install
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
- pre-commit run --all-files
537
+ prek run --all-files
530
538
  # or via uv
531
- uv run pre-commit run --all-files
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 pre-commit hooks pass.
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
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
@@ -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)