Skip to content

Conversation

nirga
Copy link
Member

@nirga nirga commented Sep 15, 2025

Summary

  • Added mcp.server parent span wrapper around FastMCP tool calls
  • Tool execution spans are now properly nested under mcp.server spans
  • Maintains existing tool span attributes and functionality

Changes

  • Modified FastMCPInstrumentor to create nested span structure:
    • Parent: mcp.server span with traceloop.span.kind="server"
    • Child: {tool_name}.tool span with existing tool attributes
  • Added comprehensive test for parent-child span relationship verification
  • Added pytest-asyncio dependency for async test support
  • Fixed linting issues in test files

Test Plan

  • New test test_fastmcp_server_mcp_parent_span verifies proper nesting
  • Existing FastMCP tests still pass
  • Linting passes without issues

🤖 Generated with Claude Code


Important

Adds mcp.server parent span wrapper for FastMCP tool calls, ensuring proper span nesting and session-level tracing.

  • Behavior:
    • Adds mcp.server parent span wrapper for FastMCP tool calls in fastmcp_instrumentation.py.
    • Tool execution spans are nested under mcp.server spans, maintaining existing attributes.
  • Instrumentation:
    • Modifies FastMCPInstrumentor to create nested span structure with mcp.server as parent and {tool_name}.tool as child.
    • Adds session-level tracing for FastMCP client sessions in instrumentation.py.
  • Tests:
    • Adds test_fastmcp_server_mcp_parent_span in test_fastmcp_server_span.py to verify span nesting.
    • Adds pytest-asyncio to pyproject.toml for async test support.
  • Misc:
    • Fixes linting issues in test files.

This description was created by Ellipsis for 3f0c4eb. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Two-level FastMCP tracing: parent MCP server spans with nested tool spans for clear parent-child relationships.
    • Session-level tracing for FastMCP client sessions (root session spans) with improved error recording and lifecycle handling.
    • Input/output serialization logged on tool spans and enhanced response reporting across spans.
  • Tests

    • Added async test validating MCP server → tool span relationships and span attributes.
  • Chores

    • Added pytest-asyncio to test deps; sample app now depends on fastmcp.

- Wrap FastMCP tool calls with mcp.server parent span
- Tool execution spans are now nested under mcp.server spans
- Add server span with traceloop.span.kind="server" attribute
- Maintain existing tool span attributes and functionality
- Add comprehensive test for parent-child span relationship
- Fix linting issues in test files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link

coderabbitai bot commented Sep 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a parent mcp.server span and nested tool spans for FastMCP calls; moves input/output logging to the tool span and records serialized response on the parent; expands error/status handling across both spans; adds client session enter/exit wrappers and Config exception_logger handling; includes an async span-hierarchy test, a fastmcp dependency in sample app, and pytest-asyncio test dependency.

Changes

Cohort / File(s) Change Summary
Server / tool tracing
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py
Replaces single-span execution with two-level spans: a parent mcp.server (span kind "server", entity "mcp.server") and a nested tool span named <entity>.tool (span kind "tool"). Moves input/output logging to the tool span, serializes outputs for TRACELOOP_ENTITY_OUTPUT, and sets MCP_RESPONSE_VALUE on the parent. Records exceptions and status on both spans.
Client session tracing & config
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py
Adds exception_logger parameter to McpInstrumentor.__init__ and stores it in Config; registers post-import hooks wrapping Client.__aenter__ and Client.__aexit__ to start/end a mcp.client.session span (span kind "session"), stores a session context manager on the client instance, and records errors on failures.
Instrumentation init change
packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
Removes inline Telemetry lambda passed as exception_logger when instantiating McpInstrumentor() in init_mcp_instrumentor(), now instantiates without that callback.
Tests: span hierarchy
packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py
Adds async test test_fastmcp_server_mcp_parent_span to assert presence and parent-child relationship between mcp.server and tool spans, verifies trace_id equality and span attributes.
Test dependency update
packages/opentelemetry-instrumentation-mcp/pyproject.toml
Adds pytest-asyncio to dev/test dependencies.
Sample app dependency
packages/sample-app/pyproject.toml
Adds fastmcp = "^2.12.3" to the sample-app dependencies.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client
  participant FastMCP as FastMCP Server
  participant Instr as Instrumentation
  participant Tool as Tool

  Client->>FastMCP: invoke tool(name,args)
  activate FastMCP
  FastMCP->>Instr: handle request
  activate Instr
  note over Instr: start parent span "mcp.server"\n(kind=server, entity=mcp.server)
  Instr->>Instr: start nested span "entity_name.tool"\n(kind=tool, entity=tool)
  note right of Instr: if prompts logging enabled:\nrecord input on tool span

  Instr->>Tool: execute(args)
  activate Tool
  Tool-->>Instr: result / exception
  deactivate Tool

  alt success
    Instr->>Instr: serialize output\nrecord on tool span\nset MCP_RESPONSE_VALUE on parent
    Instr->>Instr: set status OK on tool and parent
  else error
    Instr->>Instr: record exception on tool and parent\nset status ERROR on both
  end

  Instr-->>FastMCP: return result / raise
  deactivate Instr
  FastMCP-->>Client: deliver result / error
  deactivate FastMCP
Loading
sequenceDiagram
  autonumber
  participant C as Client (context manager)
  participant I as Instrumentation

  C->>I: __aenter__()
  activate I
  note over I: start session span "mcp.client.session"\n(kind=session)
  I-->>C: return client
  deactivate I

  C->>I: __aexit__()
  activate I
  note over I: call wrapped __aexit__, then\nend stored session context manager
  I-->>C: exit completed
  deactivate I
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • dinmukhamedm

Poem

"I hop through traces, keen and spry,
A server root, a tool nearby.
I tuck the inputs, stash the prize,
Mark errors loud, OKs as wise.
A carrot-coded trace — hop high! 🥕"

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/mcp-server-span-wrapper

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f0c4eb and 5646c34.

⛔ Files ignored due to path filters (1)
  • packages/sample-app/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • packages/sample-app/pyproject.toml (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
🧬 Code graph analysis (1)
packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py (1)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py (1)
  • McpInstrumentor (26-398)
⏰ 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). (5)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (2)
packages/sample-app/pyproject.toml (1)

60-60: Verify fastmcp ^2.12.3 compatibility with mcp ^1.7.1 and Python >=3.10,<3.13.

rg shows fastmcp is imported/used in the repo (examples: packages/sample-app/sample_app/mcp_dev_assistant_server.py; packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp*.py; packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/*.py). Dependency resolution couldn't be run in the sandbox (poetry: command not found).

Actions to run locally / in CI:

  • cd packages/sample-app
  • poetry lock --no-update
  • poetry check
  • If lock fails, run poetry lock (no --no-update) to surface resolver errors and capture the output.
  • Inspect resolved deps: poetry show --tree fastmcp mcp
  • Confirm runtime Python: python --version (must satisfy >=3.10,<3.13)
  • Run fastmcp tests to catch API breakage: pytest packages/opentelemetry-instrumentation-mcp/tests -k fastmcp

Return the lock output or resolver errors so I can re-check; if no errors, mark this resolved.

packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py (1)

1135-1138: Restore exception logger for MCP instrumentor.

Most instrumentors pass exception_logger=lambda e: Telemetry().log_exception(e); McpInstrumentor is instantiated without it at packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py:1134–1136, which reduces visibility into instrumentation errors.

  • If McpInstrumentor accepts the kwarg, replace:
    instrumentor = McpInstrumentor()
    with
    instrumentor = McpInstrumentor(exception_logger=lambda e: Telemetry().log_exception(e))
  • If it doesn't, wrap instrumentor.instrument() in try/except and call Telemetry().log_exception(e) in the except block.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title is concise and accurately summarizes the primary change — adding an mcp.server parent span wrapper that nests FastMCP tool execution spans — and aligns with the modified instrumentation and tests in the changeset, so it communicates the main intent clearly to reviewers.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Caution

Changes requested ❌

Reviewed everything up to 847d784 in 1 minute and 28 seconds. Click for details.
  • Reviewed 207 lines of code in 3 files
  • Skipped 1 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py:23
  • Draft comment:
    Remove or replace debug print statements before finalizing the tests to keep test output clean.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% While it's generally good practice to keep test output clean, these print statements serve a valuable debugging purpose for a complex test involving span relationships. They are clearly marked as debug statements and help developers understand span relationships when tests fail. The prints don't interfere with test assertions or functionality. The comment has merit since clean test output is desirable. However, in complex tests involving distributed tracing, debug output can be very valuable for troubleshooting. The debug prints provide significant value for understanding and debugging span relationships, which outweighs the minor downside of verbose test output. The comment should be deleted as these debug prints serve a valuable purpose in this specific test case and don't require immediate action.
2. packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py:125
  • Draft comment:
    Review the default logic in _should_send_prompts(). The use of (os.getenv(...) or 'true') defaults to true when unset—confirm this is the intended behavior.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.

Workflow ID: wflow_wYGGCJYXDr4UYNze

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@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

🧹 Nitpick comments (8)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py (5)

61-71: Parent mcp.server + child tool span: solid; add MCP method attribute for clarity.

Set MCP method for easier querying (MCP spec commonly uses "tools/call").

Apply:

 with self._tracer.start_as_current_span("mcp.server") as mcp_span:
     mcp_span.set_attribute(SpanAttributes.TRACELOOP_SPAN_KIND, "server")
     mcp_span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_NAME, "mcp.server")
+    mcp_span.set_attribute(SpanAttributes.MCP_METHOD_NAME, "tools/call")

72-83: Redact sensitive inputs before logging.

Arguments may contain secrets/PII. Redact common keys before serialization.

Apply:

-                            input_data = {
-                                "tool_name": entity_name,
-                                "arguments": tool_arguments
-                            }
+                            input_data = {
+                                "tool_name": entity_name,
+                                "arguments": self._redact_sensitive(tool_arguments),
+                            }

Add helper (outside this hunk):

def _redact_sensitive(self, obj):
    SENSITIVE_KEYS = {"api_key", "apikey", "authorization", "auth", "password", "token", "secret", "access_token", "refresh_token"}
    try:
        if isinstance(obj, dict):
            return {k: ("***" if k.lower() in SENSITIVE_KEYS else self._redact_sensitive(v)) for k, v in obj.items()}
        if isinstance(obj, list):
            return [self._redact_sensitive(v) for v in obj]
        return obj
    except Exception:
        return obj

84-112: Narrow try/except; use try/except/else to satisfy Ruff TRY300 and improve clarity.

Keeps exception path isolated and avoids returning inside try.

Apply:

-                    try:
-                        result = await wrapped(*args, **kwargs)
-
-                        # Add output in traceloop format to tool span
-                        if self._should_send_prompts() and result:
-                            try:
-                                # Convert FastMCP Content objects to serializable format
-                                output_data = []
-                                for item in result:
-                                    if hasattr(item, 'text'):
-                                        output_data.append({"type": "text", "content": item.text})
-                                    elif hasattr(item, '__dict__'):
-                                        output_data.append(item.__dict__)
-                                    else:
-                                        output_data.append(str(item))
-
-                                json_output = json.dumps(output_data, cls=self._get_json_encoder())
-                                truncated_output = self._truncate_json_if_needed(json_output)
-                                tool_span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, truncated_output)
-
-                                # Also add response to MCP span
-                                mcp_span.set_attribute(SpanAttributes.MCP_RESPONSE_VALUE, truncated_output)
-                            except (TypeError, ValueError):
-                                pass  # Skip output logging if serialization fails
-
-                        tool_span.set_status(Status(StatusCode.OK))
-                        mcp_span.set_status(Status(StatusCode.OK))
-                        return result
-
-                    except Exception as e:
+                    try:
+                        result = await wrapped(*args, **kwargs)
+                    except Exception as e:
                         tool_span.set_attribute(ERROR_TYPE, type(e).__name__)
                         tool_span.record_exception(e)
                         tool_span.set_status(Status(StatusCode.ERROR, str(e)))
 
                         mcp_span.set_attribute(ERROR_TYPE, type(e).__name__)
                         mcp_span.record_exception(e)
                         mcp_span.set_status(Status(StatusCode.ERROR, str(e)))
                         raise
+                    else:
+                        # Add output in traceloop format to tool span
+                        if self._should_send_prompts() and result:
+                            try:
+                                # Convert FastMCP Content objects to serializable format
+                                output_data = []
+                                for item in result:
+                                    if hasattr(item, 'text'):
+                                        output_data.append({"type": "text", "content": item.text})
+                                    elif hasattr(item, '__dict__'):
+                                        output_data.append(item.__dict__)
+                                    else:
+                                        output_data.append(str(item))
+
+                                json_output = json.dumps(output_data, cls=self._get_json_encoder())
+                                truncated_output = self._truncate_json_if_needed(json_output)
+                                tool_span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, truncated_output)
+
+                                # Also add response to MCP span
+                                mcp_span.set_attribute(SpanAttributes.MCP_RESPONSE_VALUE, truncated_output)
+                            except (TypeError, ValueError):
+                                pass  # Skip output logging if serialization fails
+
+                        tool_span.set_status(Status(StatusCode.OK))
+                        mcp_span.set_status(Status(StatusCode.OK))
+                        return result

90-107: Be defensive about non-sequence results.

If a tool returns a scalar or dict, iterating it yields keys. Guard before iterating.

Apply:

-                                output_data = []
-                                for item in result:
+                                seq = result if isinstance(result, (list, tuple)) else [result]
+                                output_data = []
+                                for item in seq:

125-130: Broaden env parsing for TRACELOOP_TRACE_CONTENT.

Accept common truthy/falsey values.

Apply:

-        return (
-            os.getenv("TRACELOOP_TRACE_CONTENT") or "true"
-        ).lower() == "true"
+        val = os.getenv("TRACELOOP_TRACE_CONTENT", "true").strip().lower()
+        return val in {"1", "true", "yes", "y", "on"}
packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py (3)

1-1: Silence unused fixture param warning.

Keep the fixture for setup but mark as intentionally unused.

Apply:

-async def test_fastmcp_server_mcp_parent_span(span_exporter, tracer_provider) -> None:
+async def test_fastmcp_server_mcp_parent_span(span_exporter, tracer_provider) -> None:  # noqa: ARG001

23-36: Remove noisy debug prints from tests.

These clutter CI output. Prefer logging only on failure.

Apply:

-    # Debug: Print span details with parent info
-    print(f"\nTotal spans: {len(spans)}")
-    for i, span in enumerate(spans):
-        parent_id = span.parent.span_id if span.parent else "None"
-        print(f"Span {i}: name='{span.name}', span_id={span.get_span_context().span_id}, "
-              f"parent_id={parent_id}, trace_id={span.get_span_context().trace_id}")
-
-    # Look specifically for mcp.server and tool spans
+    # Look specifically for mcp.server and tool spans

31-33: Tighten tool span selection to reduce false positives.

Also filter by entity name.

Apply:

-    tool_spans = [span for span in spans if span.name.endswith('.tool')]
+    tool_spans = [
+        span for span in spans
+        if span.name.endswith('.tool') and span.attributes.get('traceloop.entity.name') == 'test_tool'
+    ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8287b30 and 847d784.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-mcp/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py (1 hunks)
  • packages/opentelemetry-instrumentation-mcp/pyproject.toml (1 hunks)
  • packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py
  • packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py
🧬 Code graph analysis (2)
packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py (2)
packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py (1)
  • tool (63-73)
packages/traceloop-sdk/traceloop/sdk/utils/in_memory_span_exporter.py (1)
  • get_finished_spans (40-43)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py (1)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (2)
  • SpanAttributes (64-261)
  • TraceloopSpanKindValues (301-306)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-mcp/tests/test_fastmcp_server_span.py

1-1: Unused function argument: tracer_provider

(ARG001)

packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/fastmcp_instrumentation.py

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

(TRY300)

⏰ 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). (6)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
packages/opentelemetry-instrumentation-mcp/pyproject.toml (1)

42-42: Approve: pytest-asyncio ^1.2.0 is compatible with pytest ^8.2.x and Python 3.10–3.13. pyproject pins pytest ^8.2.2 and pytest-asyncio ^1.2.0; pytest-asyncio 1.2.0 (released Sep 12, 2025) lists Python 3.10–3.13 support and the project changelog documents pytest‑8 compatibility. (pypi.org)

@elinacse
Copy link
Contributor

@nirga Shall I take these changes and merge with my PR #3383 ?

Instrument FastMCP Client context manager to create a session-level span that ensures all MCP operations (initialize, list_tools, call_tool, list_resources, read_resource) within a client session belong to the same trace.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Caution

Changes requested ❌

Reviewed 3126550 in 1 minute and 55 seconds. Click for details.
  • Reviewed 78 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py:203
  • Draft comment:
    Using a synchronous context manager (via enter/exit) in an async wrapper may be non-idiomatic. Ensure that tracer.start_as_current_span is safe in async contexts or consider an async-compatible alternative.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% This is a tricky case. The comment raises a valid concern about mixing sync and async context managers. However, OpenTelemetry's tracer.start_as_current_span() is designed to work this way - it returns a synchronous context manager by design. The code is following the standard OpenTelemetry pattern. Making it async would actually be wrong since the underlying API is synchronous. I could be wrong about OpenTelemetry's design intentions. Maybe there is an async-specific tracing API I'm not aware of. Looking at OpenTelemetry's documentation and common usage patterns, this is definitely the standard way to use the tracing API, even in async code. The sync/async mixing is intentional in the OpenTelemetry design. Delete this comment. While it raises an interesting point about sync/async mixing, the code is correctly following OpenTelemetry's intended usage patterns.

Workflow ID: wflow_A15c6yGJjGB2jNGe

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@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: 3

🧹 Nitpick comments (1)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py (1)

41-54: Optional: also wrap if module already imported.

If fastmcp.client is imported before _instrument() runs, the post-import hook won’t fire. Consider best-effort immediate wrapping when already present in sys.modules (keep the post-import hook).

I can provide a minimal guarded snippet using sys.modules if you want it added.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 847d784 and 3126550.

📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py
🧬 Code graph analysis (1)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py (1)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/utils.py (1)
  • dont_throw (12-40)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py

209-209: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)


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

(TRY300)


222-222: Unused method argument: tracer

(ARG002)


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

(TRY300)

⏰ 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). (5)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Lint

Add exception_logger parameter to McpInstrumentor constructor to match the pattern used by other instrumentation packages. This allows users to provide a custom exception logger callback that will be called when exceptions are caught by the dont_throw decorator.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed 3f0c4eb in 1 minute and 25 seconds. Click for details.
  • Reviewed 23 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py:27
  • Draft comment:
    Consider adding a type annotation for the 'exception_logger' parameter.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
2. packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py:29
  • Draft comment:
    Assigning 'exception_logger' to a global Config variable may introduce side effects; ensure this global state usage is thread-safe and well-documented.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% This is a legitimate concern about thread-safety when modifying global state. However, the comment is somewhat speculative ("may introduce side effects") and asks for documentation rather than suggesting a specific fix. The comment also asks to "ensure" something rather than making a concrete suggestion. Per the rules, we should not keep comments that ask the author to verify or ensure things. The underlying thread-safety concern is valid and important. Perhaps I'm being too strict about the wording of the comment. While the concern is valid, the rules are clear that we should not keep comments that ask authors to "ensure" things or that are speculative. A better comment would propose a specific solution like using thread-local storage or a lock. Delete this comment since it asks the author to "ensure" something rather than making a specific suggestion, and is somewhat speculative about potential issues.

Workflow ID: wflow_O0z1jnOEg2K3XqEV

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@elinacse
Copy link
Contributor

elinacse commented Sep 16, 2025

Hi @nirga ,
These changes doesn't address this issue #3364
1st issue -
Missing MCP spans for stdio mode: The current changes don't provide response tracing for stdio mode as ResponseStreamWriter spans are missing when testing with Claude Desktop , github copilot . (PR - #3383)

2nd issue -
We're also missing RequestStreamWriter spans entirely for both stdio and http mode .
3rd issue -
The root span is missing if we are not manually stopping mcp server or quitting claude desktop when testing through copilot or claude respectively .

CC: @gyliu513

@nirga nirga changed the title feat(mcp): add mcp.server parent span wrapper for FastMCP tool calls fix(mcp): add mcp.server parent span wrapper for FastMCP tool calls Sep 16, 2025
@nina-kollman nina-kollman merged commit 0535c6a into main Sep 17, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants