microbootstrap 1.3.0__tar.gz → 1.4.0__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 (37) hide show
  1. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/PKG-INFO +28 -1
  2. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/README.md +24 -0
  3. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/__init__.py +4 -0
  4. microbootstrap-1.4.0/microbootstrap/bootstrappers/fastmcp.py +101 -0
  5. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/bootstrappers/litestar.py +2 -2
  6. microbootstrap-1.4.0/microbootstrap/config/fastmcp.py +42 -0
  7. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/prometheus_instrument.py +5 -0
  8. microbootstrap-1.4.0/microbootstrap/middlewares/fastmcp.py +46 -0
  9. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/settings.py +13 -0
  10. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/pyproject.toml +2 -1
  11. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/bootstrappers/__init__.py +0 -0
  12. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/bootstrappers/base.py +0 -0
  13. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/bootstrappers/fastapi.py +0 -0
  14. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/bootstrappers/faststream.py +0 -0
  15. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/config/__init__.py +0 -0
  16. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/config/fastapi.py +0 -0
  17. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/config/faststream.py +0 -0
  18. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/config/litestar.py +0 -0
  19. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/console_writer.py +0 -0
  20. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/exceptions.py +0 -0
  21. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/granian_server.py +0 -0
  22. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/helpers.py +0 -0
  23. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/__init__.py +0 -0
  24. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/base.py +0 -0
  25. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/cors_instrument.py +0 -0
  26. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/health_checks_instrument.py +0 -0
  27. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/instrument_box.py +0 -0
  28. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/logging_instrument.py +0 -0
  29. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/opentelemetry_instrument.py +0 -0
  30. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/pyroscope_instrument.py +0 -0
  31. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/sentry_instrument.py +0 -0
  32. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments/swagger_instrument.py +0 -0
  33. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/instruments_setupper.py +0 -0
  34. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/middlewares/__init__.py +0 -0
  35. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/middlewares/fastapi.py +0 -0
  36. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/middlewares/litestar.py +0 -0
  37. {microbootstrap-1.3.0 → microbootstrap-1.4.0}/microbootstrap/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: microbootstrap
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Package for bootstrapping new micro-services
5
5
  Keywords: python,microservice,bootstrap,opentelemetry,logging,error-tracing,litestar,fastapi
6
6
  Author: community-of-python
@@ -47,6 +47,8 @@ Requires-Dist: fastapi>=0.100 ; extra == 'fastapi'
47
47
  Requires-Dist: fastapi-offline-docs>=1 ; extra == 'fastapi'
48
48
  Requires-Dist: opentelemetry-instrumentation-fastapi>=0.46b0 ; extra == 'fastapi'
49
49
  Requires-Dist: prometheus-fastapi-instrumentator>=6.1 ; extra == 'fastapi'
50
+ Requires-Dist: fastmcp>=2,<4 ; extra == 'fastmcp'
51
+ Requires-Dist: prometheus-client>=0.20 ; extra == 'fastmcp'
50
52
  Requires-Dist: faststream~=0.6.2 ; extra == 'faststream'
51
53
  Requires-Dist: prometheus-client>=0.20 ; extra == 'faststream'
52
54
  Requires-Dist: granian[reload]>=1 ; extra == 'granian'
@@ -55,6 +57,7 @@ Requires-Dist: litestar-offline-docs>=1 ; extra == 'litestar'
55
57
  Requires-Dist: prometheus-client>=0.20 ; extra == 'litestar'
56
58
  Requires-Python: >=3.10, <4
57
59
  Provides-Extra: fastapi
60
+ Provides-Extra: fastmcp
58
61
  Provides-Extra: faststream
59
62
  Provides-Extra: granian
60
63
  Provides-Extra: litestar
@@ -110,6 +113,7 @@ Those instruments can be bootstrapped for:
110
113
  - `fastapi`,
111
114
  - `litestar`,
112
115
  - or `faststream` service,
116
+ - or `fastmcp` service,
113
117
  - or even a service that doesn't use one of these frameworks.
114
118
 
115
119
  Interested? Let's dive right in ⚡
@@ -140,6 +144,7 @@ Also, you can specify extras during installation for concrete framework:
140
144
  - `fastapi`
141
145
  - `litestar`
142
146
  - `faststream` (ASGI app)
147
+ - `fastmcp`
143
148
 
144
149
  Also we have `granian` extra that is requires for `create_granian_server`.
145
150
 
@@ -260,6 +265,28 @@ settings = YourSettings()
260
265
  application: AsgiFastStream = FastStreamBootstrapper(settings).bootstrap()
261
266
  ```
262
267
 
268
+ ### FastMCP
269
+
270
+ ```python
271
+ from fastmcp import FastMCP
272
+
273
+ from microbootstrap import FastMcpSettings
274
+ from microbootstrap.bootstrappers.fastmcp import FastMcpBootstrapper
275
+
276
+
277
+ class YourSettings(FastMcpSettings):
278
+ service_debug: bool = False
279
+ service_name: str = "my-awesome-mcp-service"
280
+ service_description: str = "MCP server for internal tools"
281
+
282
+ sentry_dsn: str = "your-sentry-dsn"
283
+
284
+
285
+ settings = YourSettings()
286
+
287
+ application: FastMCP = FastMcpBootstrapper(settings).bootstrap()
288
+ ```
289
+
263
290
  ## Settings
264
291
 
265
292
  The settings object is the core of microbootstrap.
@@ -48,6 +48,7 @@ Those instruments can be bootstrapped for:
48
48
  - `fastapi`,
49
49
  - `litestar`,
50
50
  - or `faststream` service,
51
+ - or `fastmcp` service,
51
52
  - or even a service that doesn't use one of these frameworks.
52
53
 
53
54
  Interested? Let's dive right in ⚡
@@ -78,6 +79,7 @@ Also, you can specify extras during installation for concrete framework:
78
79
  - `fastapi`
79
80
  - `litestar`
80
81
  - `faststream` (ASGI app)
82
+ - `fastmcp`
81
83
 
82
84
  Also we have `granian` extra that is requires for `create_granian_server`.
83
85
 
@@ -198,6 +200,28 @@ settings = YourSettings()
198
200
  application: AsgiFastStream = FastStreamBootstrapper(settings).bootstrap()
199
201
  ```
200
202
 
203
+ ### FastMCP
204
+
205
+ ```python
206
+ from fastmcp import FastMCP
207
+
208
+ from microbootstrap import FastMcpSettings
209
+ from microbootstrap.bootstrappers.fastmcp import FastMcpBootstrapper
210
+
211
+
212
+ class YourSettings(FastMcpSettings):
213
+ service_debug: bool = False
214
+ service_name: str = "my-awesome-mcp-service"
215
+ service_description: str = "MCP server for internal tools"
216
+
217
+ sentry_dsn: str = "your-sentry-dsn"
218
+
219
+
220
+ settings = YourSettings()
221
+
222
+ application: FastMCP = FastMcpBootstrapper(settings).bootstrap()
223
+ ```
224
+
201
225
  ## Settings
202
226
 
203
227
  The settings object is the core of microbootstrap.
@@ -8,6 +8,7 @@ from microbootstrap.instruments.opentelemetry_instrument import (
8
8
  )
9
9
  from microbootstrap.instruments.prometheus_instrument import (
10
10
  FastApiPrometheusConfig,
11
+ FastMcpPrometheusConfig,
11
12
  FastStreamPrometheusConfig,
12
13
  FastStreamPrometheusMiddlewareProtocol,
13
14
  LitestarPrometheusConfig,
@@ -17,6 +18,7 @@ from microbootstrap.instruments.sentry_instrument import SentryConfig
17
18
  from microbootstrap.instruments.swagger_instrument import SwaggerConfig
18
19
  from microbootstrap.settings import (
19
20
  FastApiSettings,
21
+ FastMcpSettings,
20
22
  FastStreamSettings,
21
23
  InstrumentsSetupperSettings,
22
24
  LitestarSettings,
@@ -27,6 +29,8 @@ __all__ = (
27
29
  "CorsConfig",
28
30
  "FastApiPrometheusConfig",
29
31
  "FastApiSettings",
32
+ "FastMcpPrometheusConfig",
33
+ "FastMcpSettings",
30
34
  "FastStreamOpentelemetryConfig",
31
35
  "FastStreamPrometheusConfig",
32
36
  "FastStreamPrometheusMiddlewareProtocol",
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+ import typing
3
+
4
+ import prometheus_client
5
+ import typing_extensions
6
+ from fastmcp import FastMCP
7
+ from starlette.responses import JSONResponse, Response
8
+
9
+ from microbootstrap.bootstrappers.base import ApplicationBootstrapper
10
+ from microbootstrap.config.fastmcp import FastMcpConfig
11
+ from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument, HealthCheckTypedDict
12
+ from microbootstrap.instruments.logging_instrument import LoggingInstrument
13
+ from microbootstrap.instruments.prometheus_instrument import FastMcpPrometheusConfig, PrometheusInstrument
14
+ from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
15
+ from microbootstrap.instruments.sentry_instrument import SentryInstrument
16
+ from microbootstrap.middlewares.fastmcp import FastMcpLoggingMiddleware
17
+ from microbootstrap.settings import FastMcpSettings
18
+
19
+
20
+ if typing.TYPE_CHECKING:
21
+ from starlette.requests import Request
22
+
23
+
24
+ class KwargsFastMCP(FastMCP[typing.Any]):
25
+ def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
26
+ super().__init__(**kwargs)
27
+
28
+
29
+ class FastMcpBootstrapper(
30
+ ApplicationBootstrapper[FastMcpSettings, FastMCP[typing.Any], FastMcpConfig],
31
+ ):
32
+ application_config = FastMcpConfig()
33
+ application_type = KwargsFastMCP
34
+
35
+ def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]:
36
+ return {
37
+ "name": self.application_config.name or self.settings.service_name,
38
+ "instructions": self.application_config.instructions or self.settings.service_description,
39
+ "version": self.application_config.version or self.settings.service_version,
40
+ }
41
+
42
+ def bootstrap_before_instruments_after_app_created(
43
+ self,
44
+ application: FastMCP[typing.Any],
45
+ ) -> FastMCP[typing.Any]:
46
+ self.console_writer.print_bootstrap_table()
47
+ return application
48
+
49
+
50
+ FastMcpBootstrapper.use_instrument()(SentryInstrument)
51
+ FastMcpBootstrapper.use_instrument()(PyroscopeInstrument)
52
+
53
+
54
+ @FastMcpBootstrapper.use_instrument()
55
+ class FastMcpLoggingInstrument(LoggingInstrument):
56
+ def bootstrap_after(self, application: FastMCP[typing.Any]) -> FastMCP[typing.Any]: # type: ignore[override]
57
+ if not self.instrument_config.logging_turn_off_middleware:
58
+ application.add_middleware(FastMcpLoggingMiddleware())
59
+ return application
60
+
61
+
62
+ @FastMcpBootstrapper.use_instrument()
63
+ class FastMcpHealthChecksInstrument(HealthChecksInstrument):
64
+ def bootstrap_after(self, application: FastMCP[typing.Any]) -> FastMCP[typing.Any]: # type: ignore[override]
65
+ @application.custom_route(
66
+ self.instrument_config.health_checks_path,
67
+ methods=["GET"],
68
+ name="health_check",
69
+ include_in_schema=self.instrument_config.health_checks_include_in_schema,
70
+ )
71
+ async def health_check_handler(request: Request) -> JSONResponse: # noqa: ARG001
72
+ response_data: HealthCheckTypedDict = self.render_health_check_data()
73
+ return JSONResponse(response_data)
74
+
75
+ return application
76
+
77
+
78
+ @FastMcpBootstrapper.use_instrument()
79
+ class FastMcpPrometheusInstrument(PrometheusInstrument[FastMcpPrometheusConfig]):
80
+ def bootstrap_after(self, application: FastMCP[typing.Any]) -> FastMCP[typing.Any]: # type: ignore[override]
81
+ if not self.instrument_config.prometheus_register_route:
82
+ return application
83
+
84
+ @application.custom_route(
85
+ self.instrument_config.prometheus_metrics_path,
86
+ methods=["GET"],
87
+ name="metrics",
88
+ include_in_schema=self.instrument_config.prometheus_metrics_include_in_schema,
89
+ )
90
+ async def metrics_handler(request: Request) -> Response: # noqa: ARG001
91
+ registry: typing.Final = self.instrument_config.prometheus_registry or prometheus_client.REGISTRY
92
+ return Response(
93
+ prometheus_client.generate_latest(registry),
94
+ headers={"content-type": prometheus_client.CONTENT_TYPE_LATEST},
95
+ )
96
+
97
+ return application
98
+
99
+ @classmethod
100
+ def get_config_type(cls) -> type[FastMcpPrometheusConfig]:
101
+ return FastMcpPrometheusConfig
@@ -160,8 +160,8 @@ class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
160
160
  def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddleware:
161
161
  return OpenTelemetryMiddleware(
162
162
  app=app,
163
- client_request_hook=self.config.client_request_hook_handler, # type: ignore[arg-type]
164
- client_response_hook=self.config.client_response_hook_handler, # type: ignore[arg-type]
163
+ client_request_hook=self.config.client_request_hook_handler,
164
+ client_response_hook=self.config.client_response_hook_handler,
165
165
  default_span_details=build_litestar_route_details_from_scope,
166
166
  excluded_urls=get_excluded_urls(self.config.exclude_urls_env_key),
167
167
  meter=self.config.meter,
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+ import dataclasses
3
+ import typing
4
+
5
+
6
+ if typing.TYPE_CHECKING:
7
+ import mcp.types
8
+ from fastmcp.client.sampling import SamplingHandler
9
+ from fastmcp.server.auth import AuthProvider
10
+ from fastmcp.server.lifespan import Lifespan
11
+ from fastmcp.server.middleware import Middleware as FastMcpMiddleware
12
+ from fastmcp.server.providers import Provider
13
+ from fastmcp.server.server import DuplicateBehavior, LifespanCallable
14
+ from fastmcp.server.transforms import Transform
15
+ from fastmcp.tools.base import Tool
16
+ from key_value.aio.protocols import AsyncKeyValue
17
+
18
+
19
+ @dataclasses.dataclass
20
+ class FastMcpConfig:
21
+ name: str | None = None
22
+ instructions: str | None = None
23
+ version: str | int | float | None = None
24
+ website_url: str | None = None
25
+ icons: list[mcp.types.Icon] | None = None
26
+ auth: AuthProvider | None = None
27
+ middleware: typing.Sequence[FastMcpMiddleware] | None = None
28
+ providers: typing.Sequence[Provider] | None = None
29
+ transforms: typing.Sequence[Transform] | None = None
30
+ lifespan: LifespanCallable | Lifespan | None = None
31
+ tools: typing.Sequence[Tool | typing.Callable[..., typing.Any]] | None = None
32
+ on_duplicate: DuplicateBehavior | None = None
33
+ mask_error_details: bool | None = None
34
+ dereference_schemas: bool = True
35
+ strict_input_validation: bool | None = None
36
+ list_page_size: int | None = None
37
+ tasks: bool | None = None
38
+ session_state_store: AsyncKeyValue | None = None
39
+ sampling_handler: SamplingHandler[typing.Any, typing.Any] | None = None
40
+ sampling_handler_behavior: typing.Literal["always", "fallback"] | None = None
41
+ client_log_level: mcp.types.LoggingLevel | None = None
42
+ experimental_capabilities: dict[str, dict[str, typing.Any]] | None = None
@@ -32,6 +32,11 @@ class FastApiPrometheusConfig(BasePrometheusConfig):
32
32
  prometheus_custom_labels: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
33
33
 
34
34
 
35
+ class FastMcpPrometheusConfig(BasePrometheusConfig):
36
+ prometheus_registry: typing.Any | None = None
37
+ prometheus_register_route: bool = True
38
+
39
+
35
40
  @typing.runtime_checkable
36
41
  class FastStreamPrometheusMiddlewareProtocol(typing.Protocol):
37
42
  def __init__(
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+ import time
3
+ import typing
4
+
5
+ import structlog
6
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
7
+
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from fastmcp.server.middleware import CallNext
11
+
12
+
13
+ fastmcp_access_logger: typing.Final = structlog.get_logger("mcp.access")
14
+
15
+
16
+ class FastMcpLoggingMiddleware(Middleware):
17
+ async def on_message(
18
+ self,
19
+ context: MiddlewareContext[typing.Any],
20
+ call_next: CallNext[typing.Any, typing.Any],
21
+ ) -> typing.Any: # noqa: ANN401
22
+ start_time: typing.Final = time.perf_counter_ns()
23
+ try:
24
+ result: typing.Final = await call_next(context)
25
+ except Exception:
26
+ fastmcp_access_logger.exception(
27
+ context.method or "unknown",
28
+ mcp={
29
+ "method": context.method,
30
+ "source": context.source,
31
+ "type": context.type,
32
+ },
33
+ duration=time.perf_counter_ns() - start_time,
34
+ )
35
+ raise
36
+
37
+ fastmcp_access_logger.info(
38
+ context.method or "unknown",
39
+ mcp={
40
+ "method": context.method,
41
+ "source": context.source,
42
+ "type": context.type,
43
+ },
44
+ duration=time.perf_counter_ns() - start_time,
45
+ )
46
+ return result
@@ -8,6 +8,7 @@ import pydantic_settings
8
8
  from microbootstrap import (
9
9
  CorsConfig,
10
10
  FastApiPrometheusConfig,
11
+ FastMcpPrometheusConfig,
11
12
  FastStreamOpentelemetryConfig,
12
13
  FastStreamPrometheusConfig,
13
14
  HealthChecksConfig,
@@ -102,6 +103,18 @@ class FastStreamSettings( # type: ignore[misc]
102
103
  asyncapi_path: str | None = "/asyncapi"
103
104
 
104
105
 
106
+ class FastMcpSettings( # type: ignore[misc]
107
+ BaseServiceSettings,
108
+ ServerConfig,
109
+ LoggingConfig,
110
+ SentryConfig,
111
+ FastMcpPrometheusConfig,
112
+ HealthChecksConfig,
113
+ PyroscopeConfig,
114
+ ):
115
+ """Settings for a fastmcp bootstrap."""
116
+
117
+
105
118
  class InstrumentsSetupperSettings( # type: ignore[misc]
106
119
  BaseServiceSettings,
107
120
  LoggingConfig,
@@ -56,7 +56,7 @@ dependencies = [
56
56
  "opentelemetry-instrumentation-asgi>=0.46b0",
57
57
  "orjson>=3.10.18",
58
58
  ]
59
- version = "1.3.0"
59
+ version = "1.4.0"
60
60
  authors = [{ name = "community-of-python" }]
61
61
 
62
62
  [project.optional-dependencies]
@@ -73,6 +73,7 @@ litestar = [
73
73
  ]
74
74
  granian = ["granian[reload]>=1"]
75
75
  faststream = ["faststream~=0.6.2", "prometheus-client>=0.20"]
76
+ fastmcp = ["fastmcp>=2,<4", "prometheus-client>=0.20"]
76
77
 
77
78
  [dependency-groups]
78
79
  dev = [