Skip to content

Conversation

@strawgate
Copy link
Collaborator

Add comprehensive documentation for integrating OpenTelemetry with FastMCP for distributed tracing and observability.

Changes

  • New integration guide at docs/integrations/opentelemetry.mdx
  • Covers logging integration with LoggingHandler
  • Demonstrates span creation via custom middleware
  • Includes production OTLP export configuration
  • Provides complete working example
  • Updated related docs with OpenTelemetry references
  • Registered in docs.json under new Observability section
  • Added working example in examples/opentelemetry_example.py

Closes #1998

Generated with Claude Code

Add comprehensive documentation for integrating OpenTelemetry with FastMCP:
- New integration guide at docs/integrations/opentelemetry.mdx
- Covers logging integration with LoggingHandler
- Demonstrates span creation via custom middleware
- Includes production OTLP export configuration
- Provides complete working example

Also update related docs:
- Add OpenTelemetry reference in middleware.mdx
- Add tip about OpenTelemetry in logging.mdx
- Register doc in docs.json under new Observability section

Example code:
- examples/opentelemetry_example.py with working weather server

Closes #1998

Co-authored-by: William Easton <[email protected]>
@marvin-context-protocol marvin-context-protocol bot added the documentation Updates to docs, examples, or guides. Primary change is documentation-related. label Oct 4, 2025
Copy link
Collaborator

@chrisguidry chrisguidry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why we wouldn't just bake the instrumentation directly into FastMCP and enable it by default? It's not harmful to have the no-op/non-recording spans if you're not exporting them anywhere. There may be a very slight performance effect, but I think it's pretty minimal. Worst case, we could give people an option to disable it if they didn't want OTEL.

Comment on lines +63 to +75
# Set up tracing
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(trace_provider)

# Set up logging
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogExporter()))
set_logger_provider(logger_provider)

# Attach OpenTelemetry to FastMCP's logger
fastmcp_logger = get_logger("my_server")
fastmcp_logger.addHandler(LoggingHandler(logger_provider=logger_provider))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach would be considered "manual" instrumentation, which is always an option, but wouldn't most folks want something like the "Zero Code" version: https://opentelemetry.io/docs/zero-code/python/

I think we should at least point folks to this as the default, and then we can explain in more detail the manual approach.

I do think there are some additional envvars or CLI switches required to get logging setup with the zero code approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this a little more? What we would have to do to give folks the zero code experience?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sure! The OTEL libraries (at least for Python, but I think for most languages) make a pretty strong distinction between producing telemetry (opentelemetry-api) and exporting it (opentelemetry-sdk) and they offer these zero-code approaches where you are essentially wrapping your entrypoint with opentelemetry-instrument (in Python) so that your code doesn't need to set up any exporters. Their canonical example looks like this:

opentelemetry-instrument \
    --traces_exporter console,otlp \
    --metrics_exporter console \
    --service_name your-service-name \
    --exporter_otlp_endpoint 0.0.0.0:4317 \
    python myapp.py

What would happen here is that your myapp.py might be using opentelemetry-api to make spans and metrics, and then when you run the program this way, the spans get exported to whichever ones you've configured there on the CLI. This example is very similar in spirit to what you laid out here, setting up OTLP exporters and attaching them to the providers, it's just handled by opentelemetry-instrument.

So my proposal is just to point people to that link for how they'd run their fastmcp servers by, say, wrapping them like:

opentelemetry-instrument \
    --traces_exporter console,otlp \
    --metrics_exporter console \
    --service_name my-mcp-server \
    --exporter_otlp_endpoint 0.0.0.0:4317 \
    fastmcp run mymcpserver.py


### Basic Tracing Middleware

Create a middleware that emits spans for all MCP requests:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just ship this middleware with FastMCP? I'd argue it should be on by default, even.

@strawgate
Copy link
Collaborator Author

strawgate commented Oct 12, 2025

@chrisguidry I had Claude generate this, I was mostly going to stick with logging for now

I thought the problem with emitting spans was going to be that we don't have a way to accept or propagate the trace id through MCP calls so the spans would all be uncorrelated

I'd be very happy to do more if we thought it was useful and sensible

@chrisguidry
Copy link
Collaborator

we don't have a way to accept or propagate the trace id through MCP calls

Yep, that's a great call, and I think it might need to be pushed lower into the base SDK? I would make an argument that even without trace propagation, there's still some value because you'd see all of the things your MCP server called out to, even if each tool call was a separate trace. Also, as soon as the community figures out client-side trace propagation, it would all "just work" (LOL).

Didn't mean to block your efforts to get great documentation in for sure!

@strawgate
Copy link
Collaborator Author

we don't have a way to accept or propagate the trace id through MCP calls

Yep, that's a great call, and I think it might need to be pushed lower into the base SDK? I would make an argument that even without trace propagation, there's still some value because you'd see all of the things your MCP server called out to, even if each tool call was a separate trace. Also, as soon as the community figures out client-side trace propagation, it would all "just work" (LOL).

Didn't mean to block your efforts to get great documentation in for sure!

I'm ok adding it to our client and server as experimental until the lower level sdk gets it together, would that be interesting for you?

@pamelafox
Copy link

I found this PR when trying to add otel instrumentation to a FastMCP server. I notice no mention of this package:
https://github.com/traceloop/openllmetry/blob/main/packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py

Would that package not be recommended? Is that something you'd add to FastMCP itself?

@pamelafox
Copy link

Update: I brought in the middleware from your PR and got it working with two OTel-compliant platforms. I also added in StarletteMiddleware on the FastMCP's Starlette app instance, for route-level tracing too.
You can see my usage here:
https://github.com/Azure-Samples/python-mcp-demos/pull/10/files#diff-2f9b1ef748487a1f4ade8aee44108cafa9db539ad34132e96912588d96e609de

I think it'd be great if this was added to FastMCP itself.
You may want to add options to turn off sending tool arguments in the spans, for PII/size reasons.

@strawgate
Copy link
Collaborator Author

strawgate commented Dec 3, 2025

@pamelafox im not sure using middleware makes the most sense here but im glad it worked for your use-case

@strawgate
Copy link
Collaborator Author

/marvin let's update the PR to bake the instrumentation right in

@marvin-context-protocol
Copy link
Contributor

marvin-context-protocol bot commented Dec 3, 2025

Claude finished @strawgate's task —— View job


Baking OpenTelemetry instrumentation into FastMCP ✓

Successfully integrated OpenTelemetry instrumentation directly into FastMCP core!

Completed Tasks

  • Review current OpenTelemetry documentation and examples
  • Review FastMCP architecture and middleware system
  • Design integration approach (built-in vs optional)
  • Implement built-in OTel middleware
  • Add optional dependencies for opentelemetry-api
  • Update documentation
  • Update examples
  • Create tests for the middleware
  • Run development workflow (uv sync, prek, pytest)
  • Commit and push changes

What Was Implemented

Built-in OpenTelemetry Middleware (src/fastmcp/server/middleware/opentelemetry.py):

  • Automatic span creation for all MCP operations:
    • Tool calls (tool.{name})
    • Resource reads (resource.read)
    • Prompt retrievals (prompt.{name})
    • List operations (tools.list, resources.list, etc.)
  • Graceful degradation: becomes no-op when OpenTelemetry not installed
  • Configuration options:
    • enabled: explicitly enable/disable (default: True)
    • include_arguments: control inclusion of sensitive data (default: True)
    • max_argument_length: prevent oversized spans (default: 500)
    • tracer_name: custom tracer naming (default: "fastmcp")

Optional Dependency:

  • Added fastmcp[opentelemetry] optional dependency
  • Installs opentelemetry-api>=1.20.0 and opentelemetry-sdk>=1.20.0

Updated Documentation:

  • Rewrote docs/integrations/opentelemetry.mdx to highlight built-in support
  • Added Quick Start section showing simple usage
  • Updated all examples to use built-in middleware
  • Documented configuration options and best practices

Tests:

  • Comprehensive test suite with 11 tests covering:
    • No-op behavior without OpenTelemetry installed
    • Configuration options
    • All MCP operation types
    • Error handling and propagation

Usage

Users can now simply add the middleware:

from fastmcp import FastMCP
from fastmcp.server.middleware.opentelemetry import OpenTelemetryMiddleware

mcp = FastMCP("My Server")
mcp.add_middleware(OpenTelemetryMiddleware())

If OpenTelemetry isn't installed, the middleware safely becomes a no-op. To enable full instrumentation:

pip install fastmcp[opentelemetry]

All validation checks pass ✓

--- | Branch: claude/issue-1998-20251004-0217

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

This PR adds OpenTelemetry observability to the docs, examples, and server middleware. It inserts an "Observability" group under Integrations in docs/docs.json and adds docs/integrations/opentelemetry.mdx. It updates docs/servers/logging.mdx and docs/servers/middleware.mdx with references to the OpenTelemetry guide. A new example examples/opentelemetry_example.py demonstrates setup and usage. It also introduces OpenTelemetryMiddleware at src/fastmcp/server/middleware/opentelemetry.py with tracing hooks and configuration options.

Pre-merge checks and finishing touches

❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and generic. '[Draft]' indicates work in progress, and 'Document OpenTelemetry integration' is overly broad without specifying what is being documented or the scope of changes. Update the title to be more specific and remove '[Draft]' if the PR is ready for review, e.g., 'Add OpenTelemetry integration guide and middleware' or 'Document OpenTelemetry logging and tracing with FastMCP'.
Description check ❓ Inconclusive The description covers key changes and includes a reference to the linked issue (#1998), but the Contributors and Review Checklists are incomplete with all boxes unchecked, indicating the author has not completed the required self-review steps. Complete the Contributors and Review Checklists by checking the appropriate boxes to confirm testing, documentation updates, self-review, and readiness for review.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses issue #1998 by adding comprehensive OpenTelemetry documentation, including logging integration, span creation via middleware, production configuration, and examples, fully meeting the documented objective.
Out of Scope Changes check ✅ Passed All changes are within scope of issue #1998: documentation files (opentelemetry.mdx, updates to logging.mdx and middleware.mdx), a public middleware class implementation, and an example file demonstrating OpenTelemetry integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/issue-1998-20251004-0217

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
examples/opentelemetry_example.py (1)

121-213: Example tools and error handling are appropriate as a “happy path” demo

The three tools (get_weather, get_forecast, convert_temperature) demonstrate logging at different levels and basic validation without overcomplicating control flow. The explicit error messages and simple ValueError usage are fine here, and I’d ignore Ruff’s stylistic TRY*** suggestions for this educational example.

No changes needed from my side.

Based on learnings, keeping this as a clear happy‑path example without extra defensive layers is preferable.

Also applies to: 220-234

🧹 Nitpick comments (1)
docs/integrations/opentelemetry.mdx (1)

21-112: Surface zero-code instrumentation as the recommended starting point and clarify logging API stability

Two important additions to strengthen this guide:

  1. Lead with opentelemetry-instrument as the default approach
    The official OpenTelemetry Python documentation promotes opentelemetry-instrument as the zero-code instrumentation path. Add a brief section before "Logging Integration" that explains this is the recommended way to get started, then clarify that the rest of this page covers manual/fine-grained programmatic setup for cases where you need custom configuration. Include a link to OpenTelemetry's zero-code Python guide. This helps readers quickly find the simplest path without having to read the entire manual setup.

  2. Add forward-compatibility note for logging APIs
    The official OpenTelemetry Python documentation explicitly states the Logs API and SDK are still under active development. Add a note near the logging setup code (e.g., under "Basic Setup" or at the start of "Logging Integration") stating: "OpenTelemetry's Python Logs API is currently under development. If import paths change in future versions, refer to the official OpenTelemetry Python logs documentation." This prevents the guide from becoming stale as the logging signal stabilizes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee63405 and c6d6fb4.

📒 Files selected for processing (5)
  • docs/docs.json (1 hunks)
  • docs/integrations/opentelemetry.mdx (1 hunks)
  • docs/servers/logging.mdx (1 hunks)
  • docs/servers/middleware.mdx (1 hunks)
  • examples/opentelemetry_example.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/servers/logging.mdx
  • docs/integrations/opentelemetry.mdx
  • docs/servers/middleware.mdx
🧠 Learnings (1)
📚 Learning: 2025-11-03T17:36:13.363Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 2355
File: docs/clients/client.mdx:226-246
Timestamp: 2025-11-03T17:36:13.363Z
Learning: In FastMCP documentation, prefer showing the happy path in onboarding examples without over-explaining edge cases or adding defensive checks, as this reduces cognitive burden for new users learning the API.

Applied to files:

  • docs/integrations/opentelemetry.mdx
🧬 Code graph analysis (1)
examples/opentelemetry_example.py (2)
src/fastmcp/server/middleware/middleware.py (2)
  • Middleware (80-208)
  • MiddlewareContext (48-64)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
🪛 Ruff (0.14.7)
examples/opentelemetry_example.py

92-92: Consider moving this statement to an else block

(TRY300)


164-164: Avoid specifying long messages outside the exception class

(TRY003)


198-198: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (3)
docs/servers/logging.mdx (1)

10-14: OpenTelemetry cross-link in logging docs looks good

The added sentence clearly routes readers from client logging to the broader OpenTelemetry observability guide and matches the new /integrations/opentelemetry path. No changes needed.

docs/servers/middleware.mdx (1)

596-600: Logging middleware Tip cleanly surfaces OpenTelemetry for production

The new intro sentence and Tip succinctly explain what the logging middleware does and point production users to the OpenTelemetry guide. This is consistent with the rest of the middleware docs and the new Observability section.

docs/docs.json (1)

216-222: Observability navigation group is well-placed and consistent

Adding an “Observability” group with integrations/opentelemetry under Integrations makes the new guide discoverable and matches the page path. No issues here.

Comment on lines 491 to 500
```python
from fastmcp.server.middleware.timing import TimingMiddleware
from fastmcp.server.middleware.logging import LoggingMiddleware

# Order matters: error handling first, then tracing, then logging
mcp.add_middleware(ErrorHandlingMiddleware())
mcp.add_middleware(OpenTelemetryMiddleware()) # Your custom middleware
mcp.add_middleware(TimingMiddleware()) # Built-in timing
mcp.add_middleware(LoggingMiddleware()) # Built-in logging
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Import for ErrorHandlingMiddleware missing in integration example

In the “Integration with FastMCP Middleware” example, you call ErrorHandlingMiddleware() but only import TimingMiddleware and LoggingMiddleware. As written, that snippet won’t run.

I’d suggest adding the import to keep the example copy‑pasteable:

-from fastmcp.server.middleware.timing import TimingMiddleware
-from fastmcp.server.middleware.logging import LoggingMiddleware
+from fastmcp.server.middleware.timing import TimingMiddleware
+from fastmcp.server.middleware.logging import LoggingMiddleware
+from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```python
from fastmcp.server.middleware.timing import TimingMiddleware
from fastmcp.server.middleware.logging import LoggingMiddleware
# Order matters: error handling first, then tracing, then logging
mcp.add_middleware(ErrorHandlingMiddleware())
mcp.add_middleware(OpenTelemetryMiddleware()) # Your custom middleware
mcp.add_middleware(TimingMiddleware()) # Built-in timing
mcp.add_middleware(LoggingMiddleware()) # Built-in logging
```
from fastmcp.server.middleware.timing import TimingMiddleware
from fastmcp.server.middleware.logging import LoggingMiddleware
from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
# Order matters: error handling first, then tracing, then logging
mcp.add_middleware(ErrorHandlingMiddleware())
mcp.add_middleware(OpenTelemetryMiddleware()) # Your custom middleware
mcp.add_middleware(TimingMiddleware()) # Built-in timing
mcp.add_middleware(LoggingMiddleware()) # Built-in logging
🤖 Prompt for AI Agents
In docs/integrations/opentelemetry.mdx around lines 491 to 500 the example uses
ErrorHandlingMiddleware() but does not import it; add an import for
ErrorHandlingMiddleware alongside the other middleware imports at the top of the
snippet (e.g., from fastmcp.server.middleware.error import
ErrorHandlingMiddleware) so the example is copy-paste runnable.

Comment on lines +40 to +58
# Define service metadata
resource = Resource(
attributes={
"service.name": "fastmcp-weather-server",
"service.version": "1.0.0",
"deployment.environment": "development",
}
)

# Configure tracing
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(trace_provider)

# Configure logging
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogExporter()))
set_logger_provider(logger_provider)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the FastMCP logging utilities and check for logging configuration
find . -name "*.py" -type f | head -20

Repository: jlowin/fastmcp

Length of output: 995


🏁 Script executed:

# Search for logging configuration patterns
rg -n "basicConfig|setLevel|get_logger" --type=py | head -30

Repository: jlowin/fastmcp

Length of output: 2532


🏁 Script executed:

# Look at the opentelemetry_example.py file directly
fd "opentelemetry_example.py" -type f

Repository: jlowin/fastmcp

Length of output: 228


🏁 Script executed:

# Read the logging utilities to understand get_logger implementation
cat -n src/fastmcp/utilities/logging.py

Repository: jlowin/fastmcp

Length of output: 8842


🏁 Script executed:

# Read the opentelemetry_example.py file
cat -n examples/opentelemetry_example.py

Repository: jlowin/fastmcp

Length of output: 9292


Set logger level so INFO logs reach OpenTelemetry

The OTEL logging pipeline and LoggingHandler wiring look solid, but get_logger("weather") doesn't set a level here. Python's default effective level is WARNING, so your many logger.info(...) calls won't reach the OTEL handler.

Add logger.setLevel(logging.INFO) before attaching the handler:

+import logging
...
 logger = get_logger("weather")
+logger.setLevel(logging.INFO)
 logger.addHandler(LoggingHandler(logger_provider=logger_provider))

Also applies to: 110-113

🤖 Prompt for AI Agents
In examples/opentelemetry_example.py around lines 40-58 (and also apply same
change at 110-113), the logger created with get_logger("weather") does not have
an explicit level so INFO messages are blocked by the default WARNING level; set
the logger's level to logging.INFO (i.e., call logger.setLevel(logging.INFO))
before attaching any OpenTelemetry LoggingHandler so INFO logs are emitted to
the OTEL pipeline, and repeat the same change where the logger is created at
lines 110-113.

- Create OpenTelemetryMiddleware with automatic span creation for all MCP operations
- Add opentelemetry as optional dependency (pip install fastmcp[opentelemetry])
- Gracefully degrades to no-op when OpenTelemetry not installed
- Configuration options for privacy (include_arguments) and performance (max_argument_length)
- Update documentation to reflect built-in support
- Update example to use built-in middleware
- Add comprehensive tests

Co-authored-by: William Easton <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
examples/opentelemetry_example.py (1)

65-67: Set logger level to ensure INFO logs reach OpenTelemetry.

Without an explicit level, Python's default effective level is WARNING, so the logger.info(...) calls in the tool functions won't reach the OpenTelemetry handler. Add logger.setLevel(logging.INFO) after getting the logger.

+import logging
+
 # Attach OpenTelemetry to FastMCP's logger
 logger = get_logger("weather")
+logger.setLevel(logging.INFO)
 logger.addHandler(LoggingHandler(logger_provider=logger_provider))
🧹 Nitpick comments (3)
src/fastmcp/server/middleware/opentelemetry.py (3)

126-137: Consider more specific return type annotation.

The return type dict could be more specific as dict[str, Any] for better type checking.

-    def _create_span_attributes(self, context: MiddlewareContext, **extra: Any) -> dict:
+    def _create_span_attributes(self, context: MiddlewareContext, **extra: Any) -> dict[str, Any]:

139-141: Type annotations could be more specific to match base class.

The base Middleware class uses specific types like MiddlewareContext[mt.CallToolRequestParams] and CallNext[mt.CallToolRequestParams, ToolResult]. While Any works, matching the base class signatures would improve type safety.


185-187: Remove unnecessary f-string prefix.

The span name f"resource.read" contains no placeholders, so the f prefix is extraneous.

         with self.tracer.start_as_current_span(  # type: ignore[union-attr]
-            f"resource.read", attributes=span_attributes
+            "resource.read", attributes=span_attributes
         ) as span:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6d6fb4 and 8ecce7a.

⛔ Files ignored due to path filters (3)
  • pyproject.toml is excluded by none and included by none
  • tests/server/middleware/test_opentelemetry_middleware.py is excluded by none and included by none
  • uv.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (3)
  • docs/integrations/opentelemetry.mdx (1 hunks)
  • examples/opentelemetry_example.py (1 hunks)
  • src/fastmcp/server/middleware/opentelemetry.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/integrations/opentelemetry.mdx
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must be version ≥3.10 with full type annotations
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if they're shorter
Never use bare except in code - be specific with exception types

Files:

  • src/fastmcp/server/middleware/opentelemetry.py
🧠 Learnings (2)
📚 Learning: 2025-11-03T17:36:13.363Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 2355
File: docs/clients/client.mdx:226-246
Timestamp: 2025-11-03T17:36:13.363Z
Learning: In FastMCP documentation, prefer showing the happy path in onboarding examples without over-explaining edge cases or adding defensive checks, as this reduces cognitive burden for new users learning the API.

Applied to files:

  • docs/integrations/opentelemetry.mdx
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Always include realistic error handling in code examples in MDX documentation

Applied to files:

  • docs/integrations/opentelemetry.mdx
🧬 Code graph analysis (1)
src/fastmcp/server/middleware/opentelemetry.py (1)
src/fastmcp/server/middleware/middleware.py (3)
  • CallNext (43-44)
  • Middleware (80-208)
  • MiddlewareContext (48-64)
🪛 Ruff (0.14.7)
examples/opentelemetry_example.py

119-119: Avoid specifying long messages outside the exception class

(TRY003)


153-153: Avoid specifying long messages outside the exception class

(TRY003)

src/fastmcp/server/middleware/opentelemetry.py

164-164: Consider moving this statement to an else block

(TRY300)


186-186: f-string without any placeholders

Remove extraneous f prefix

(F541)


191-191: Consider moving this statement to an else block

(TRY300)


221-221: Consider moving this statement to an else block

(TRY300)


243-243: Consider moving this statement to an else block

(TRY300)


265-265: Consider moving this statement to an else block

(TRY300)


287-287: Consider moving this statement to an else block

(TRY300)


309-309: Consider moving this statement to an else block

(TRY300)

🔇 Additional comments (14)
docs/integrations/opentelemetry.mdx (7)

1-5: LGTM!

Frontmatter is correctly structured with required title and description fields, plus an appropriate icon.


21-57: LGTM!

The Quick Start section provides a clear, minimal example showing the happy path. The graceful degradation note and installation options are well-documented.


154-199: LGTM!

The middleware configuration options and traced operations are well-documented and align with the implementation in opentelemetry.py.


412-427: LGTM!

The middleware integration example correctly imports only the middleware that is used. The execution order explanation is helpful for understanding the observability pipeline.


234-286: LGTM!

The complete example demonstrates a production-ready setup combining logging and tracing with realistic tool implementation.


387-406: LGTM!

The security guidance with the redaction example is practical and demonstrates a clear pattern for avoiding sensitive data in traces.


70-72: This review comment is unnecessary. The documentation already contains a clear stability warning in a <Note> component (line 50-56) that explicitly states: "Tracing and metrics are stable, while logging is in active development." This note appears immediately before the Logging Integration section and adequately informs users that the logging API is under active development and subject to change. The underscore-prefixed imports are the correct and documented approach for using OpenTelemetry Python logging at this time.

Likely an incorrect or invalid review comment.

src/fastmcp/server/middleware/opentelemetry.py (2)

42-49: LGTM!

The optional dependency handling is well-implemented - catches ImportError specifically and sets a module-level flag for graceful degradation.


227-313: LGTM!

All list operation handlers follow a consistent pattern with proper span lifecycle management, attribute recording, and exception handling.

examples/opentelemetry_example.py (5)

39-56: LGTM!

The OpenTelemetry configuration follows best practices with batch processors and proper resource attributes for service identification.


77-101: LGTM!

The get_weather tool demonstrates proper logging integration with the OpenTelemetry pipeline using realistic mock data.


104-133: LGTM!

The get_forecast tool demonstrates input validation with appropriate error handling and logging at multiple levels (INFO for normal operations, WARNING for invalid input).


136-168: LGTM!

The convert_temperature tool demonstrates comprehensive logging at different levels (DEBUG, INFO, ERROR) and proper input validation with unit conversion logic.


175-189: LGTM!

The main block provides helpful context for users running the example, with clear guidance about production deployment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Updates to docs, examples, or guides. Primary change is documentation-related.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document logging with OpenTelemetry

4 participants