Skip to content

Conversation

saqadri
Copy link
Collaborator

@saqadri saqadri commented Sep 4, 2025

Clean up OpenTelemetrySettings to be able to specify multiple exporters. This allows you to output to console, file, and even multiple otlp collector endpoints.

Particularly nice for MCP-agent cloud, where a developer can configure their agent to export to their own OTEL endpoint (while still allowing us to configure out internal OTLP endpoint for surfacing logs to them).

Summary by CodeRabbit

  • New Features

    • Added sampling-based tracing with configurable sample rate.
    • Introduced structured OpenTelemetry exporters (console, file with path settings, OTLP with endpoint/headers) with backward compatibility.
    • Added configurable log path settings separate from trace paths.
  • Documentation

    • Updated tracing examples and READMEs to use typed OTLP exporter configuration and multi-exporter YAML snippets.
  • Refactor

    • Simplified configuration by removing per-server allowed tool allowlist from settings.
  • Tests

    • Expanded coverage for exporter configurations and tool filtering; updated tests to use the new file exporter settings.
  • Style

    • Minor formatting and import cleanups.

@saqadri saqadri requested a review from rholinshead September 4, 2025 22:23
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Migrates OpenTelemetry exporter config from string lists and otlp_settings to typed exporter objects across examples, schema, and core code. Adds sampling (ParentBased + TraceIdRatioBased) in tracer, merges OTLP headers, introduces LogPathSettings, updates JSON schema with discriminated union, removes allowed_tools from MCPServerSettings, and adjusts tests and docs.

Changes

Cohort / File(s) Summary
Tracing docs: switch to typed exporters
examples/tracing/agent/README.md, examples/tracing/langfuse/README.md, examples/tracing/llm/README.md, examples/tracing/mcp/README.md, examples/tracing/temporal/README.md
Replace otel.otlp_settings endpoint instructions with YAML using otel.enabled and typed exporters (console/file/otlp) including endpoint examples.
Tracing example configs: typed exporter objects
examples/tracing/agent/mcp_agent.config.yaml, examples/tracing/langfuse/mcp_agent.config.yaml, examples/tracing/llm/mcp_agent.config.yaml, examples/tracing/mcp/mcp_agent.config.yaml, examples/tracing/temporal/mcp_agent.config.yaml
Convert exporters from string arrays to lists of objects with type; inline OTLP endpoint under the otlp exporter; remove otlp_settings blocks; preserve service_name and enabled.
Workflow example configs: typed exporters + file path pattern
examples/workflows/workflow_deep_orchestrator/mcp_agent.config.yaml, examples/workflows/workflow_evaluator_optimizer/mcp_agent.config.yaml, examples/workflows/workflow_intent_classifier/mcp_agent.config.yaml, examples/workflows/workflow_orchestrator_worker/mcp_agent.config.yaml, examples/workflows/workflow_parallel/mcp_agent.config.yaml
Change exporters to typed list (e.g., - type: console/file). Add file path_settings with path_pattern and unique_id in deep_orchestrator; remove otlp_settings; add commented OTLP exporter guidance.
Secrets example note
examples/tracing/langfuse/mcp_agent.secrets.yaml.example
Add comment that headers are merged with typed OTLP exporter settings; no functional changes.
Schema: discriminated exporters, removals
schema/mcp-agent.config.schema.json
Add ConsoleExporterSettings, FileExporterSettings, OTLPExporterSettings; exporters uses discriminator on type; remove OpenTelemetrySettings.otlp_settings/path/path_settings and MCPServerSettings.allowed_tools; keep file/path settings under FileExporterSettings.
Core config models and migration
src/mcp_agent/config.py
Implement typed exporter models and union; add LogPathSettings and use in LoggerSettings; remove MCPServerSettings.allowed_tools; add pre-validation coercion from legacy exporters/otlp_settings/path to typed form.
Tracer: sampling and typed exporter handling
src/mcp_agent/tracing/tracer.py
Add ParentBased(TraceIdRatioBased) sampling from settings.sample_rate; support typed exporters and legacy strings; merge OTLP endpoint/headers; handle file exporter path/path_settings from typed/global settings.
CLI formatting only
src/mcp_agent/cli/cloud/commands/auth/whoami/main.py, src/mcp_agent/cli/cloud/main.py
Whitespace and import formatting; no behavior changes.
MCP aggregator formatting
src/mcp_agent/mcp/mcp_aggregator.py
Style-only reformatting around allowed_tools filter; logic unchanged.
Tests: aggregator coverage and tracing config
tests/mcp/test_mcp_aggregator.py, tests/test_tracing_isolation.py
Expand mocks and scenarios for tool filtering; switch tests to use FileExporterSettings for file exporter; update imports; broaden coverage without prod code changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UserApp as App startup
  participant Config as OpenTelemetrySettings (config)
  participant Coercion as Legacy→Typed Coercion
  participant Tracer as Tracer init
  participant OTel as TracerProvider
  participant Exp as Exporters

  UserApp->>Config: Load YAML (exporters, sample_rate, paths, headers)
  Config->>Coercion: model_validator (mode="before")
  Coercion-->>Config: Convert strings + otlp_settings/path → typed exporters
  Config-->>Tracer: Typed exporters list + sample_rate

  Note over Tracer,OTel: Initialize provider with sampling
  Tracer->>OTel: TracerProvider(resource, sampler=ParentBased(TraceIdRatioBased(rate)))

  loop For each exporter
    Tracer->>Exp: Create exporter by type
    alt type=console
      Exp-->>Tracer: Console exporter
    else type=file
      Exp-->>Tracer: File exporter (path or path_settings)
    else type=otlp
      Tracer->>Exp: Merge endpoint + headers (typed + secrets)
      Exp-->>Tracer: OTLP exporter (endpoint, headers)
    else unknown
      Tracer-->>Tracer: Log error (skip)
    end
  end

  Tracer-->>UserApp: Global tracer ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • petersonbill64
  • StreetLamb

Poem

In burrows of YAML I hop and I trace,
Typed little carrots in each exporter’s place.
Samplers nibble ratios, headers nicely merge—
Console, file, OTLP, all ready to surge.
With logs on a pattern and paths lined just right,
I thump out spans—then vanish into night. 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/multiple_otel_exporters

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
src/mcp_agent/mcp/mcp_aggregator.py (4)

858-869: Bug: parameter ‘server_name’ is shadowed and discarded.

The local assignment resets server_name to None, ignoring a caller-provided server_name.

-            server_name: str = None
-            local_tool_name: str = None
-
-            if server_name:
+            local_tool_name: str | None = None
+            if server_name is not None:
                 span.set_attribute("server_name", server_name)
                 local_tool_name = name
             else:
-                server_name, local_tool_name = await self._parse_capability_name(
+                parsed_server_name, local_tool_name = await self._parse_capability_name(
                     name, "tool"
                 )
+                server_name = parsed_server_name
                 span.set_attribute("parsed_server_name", server_name)
                 span.set_attribute("parsed_tool_name", local_tool_name)

1406-1419: Bug: returning wrong type from _call_tool.

Success path returns result.content (list), while the method is annotated to return CallToolResult. Also error path returns CallToolResult. Return the full result for consistency.

     async def _call_tool(
         self, name: str, arguments: dict | None = None
     ) -> CallToolResult:
         """Call a specific tool from the aggregated servers."""
         try:
             result = await self.aggregator.call_tool(name=name, arguments=arguments)
-            return result.content
+            return result
         except Exception as e:
             return CallToolResult(
                 isError=True,
                 content=[
                     TextContent(type="text", text=f"Error calling tool: {str(e)}")
                 ],
             )

1444-1447: Bug: _list_resources returns the wrapper instead of the list.

Unwrap resources.resources like the analogous tools/prompts methods.

     async def _list_resources(self):
         """List available resources from the connected MCP servers."""
-        resources = await self.aggregator.list_resources()
-        return resources
+        resources_result = await self.aggregator.list_resources()
+        return resources_result.resources

536-548: Broken temp connection lifecycle in get_server().

Returning a client from inside an async with closes it immediately; callers receive a closed session.

Minimal safe fix: do not attempt to return a temporary connection; force callers to use context-managed flows.

         else:
-            logger.debug(
-                f"Creating temporary connection to server: {server_name}",
-                data={
-                    "progress_action": ProgressAction.STARTING,
-                    "server_name": server_name,
-                    "agent_name": self.agent_name,
-                },
-            )
-            async with gen_client(
-                server_name, server_registry=self.context.server_registry
-            ) as client:
-                return client
+            logger.debug(
+                "Non-persistent mode: use gen_client(...) in a context manager to obtain a temporary session.",
+                data={"server_name": server_name, "agent_name": self.agent_name},
+            )
+            return None

If you want, I can propose an asynccontextmanager wrapper to yield a temporary session.

examples/tracing/langfuse/README.md (1)

68-76: Fix section numbering (missing step “3”).

Change “## 4 Run locally” to “## 3 Run locally” to avoid confusion.

-## `4` Run locally
+## `3` Run locally
src/mcp_agent/tracing/tracer.py (1)

208-215: Wrong package name in OpenAI instrumentation hint; severity could be reduced.

The message recommends the Anthropic package for OpenAI. Suggest correcting the package and using warning.

-            except ModuleNotFoundError:
-                logger.error(
-                    "OpenAI OTEL instrumentation not available. Please install opentelemetry-instrumentation-anthropic."
-                )
+            except ModuleNotFoundError:
+                logger.warning(
+                    "OpenAI OTEL instrumentation not available. Please install opentelemetry-instrumentation-openai."
+                )
schema/mcp-agent.config.schema.json (1)

687-693: Typo: “Streamble HTTP” → “Streamable HTTP”.

User-facing description string.

Apply this change in the description text.

🧹 Nitpick comments (31)
examples/tracing/temporal/README.md (2)

52-58: Showcase multiple exporters in the example.

Since the PR enables multiple exporters, add a second exporter (e.g., console) to make this immediately discoverable.

 otel:
   enabled: true
   exporters:
     - type: otlp
-      endpoint: "http://localhost:4318/v1/traces"
+      endpoint: "http://localhost:4318/v1/traces"
+    - type: console

50-50: Use a stable Jaeger docs link to reduce rot.

-[Run Jaeger locally](https://www.jaegertracing.io/docs/2.5/getting-started/) and then ensure the `mcp_agent.config.yaml` for this example includes a typed OTLP exporter with the collector endpoint:
+[Run Jaeger locally](https://www.jaegertracing.io/docs/latest/getting-started/) and then ensure the `mcp_agent.config.yaml` for this example includes a typed OTLP exporter with the collector endpoint:
examples/tracing/agent/README.md (1)

13-23: Docs: show multiple OTLP exporters to highlight the new capability.

Consider expanding the snippet to demonstrate two OTLP endpoints (e.g., developer and internal) plus console/file to make the feature obvious.

Apply this diff to the YAML block:

 otel:
   enabled: true
   exporters:
     - type: console
-    - type: file
-    - type: otlp
-      endpoint: "http://localhost:4318/v1/traces"
+    - type: file
+    - type: otlp
+      endpoint: "http://localhost:4318/v1/traces"
+    - type: otlp
+      endpoint: "https://internal-collector:4318/v1/traces"
+      headers:
+        Authorization: "Bearer ${INTERNAL_OTEL_TOKEN}"
examples/tracing/langfuse/mcp_agent.secrets.yaml.example (1)

10-10: Clarify header precedence in docs.

Since headers are merged, explicitly state which source wins on key conflicts.

Apply this doc tweak:

-  # Headers are merged with typed OTLP exporter settings
+  # Headers are merged with typed OTLP exporter settings (please document precedence, e.g., exporter.headers overrides otel.otlp_settings.headers on key conflicts)
examples/tracing/mcp/README.md (1)

51-59: Docs: add in-snippet comments for endpoint scope and optional extra exporters.

Small comments make it clear this path is for traces and that multiple exporters are supported.

Apply this diff to the YAML block:

 otel:
   enabled: true
   exporters:
-    - type: otlp
-      endpoint: "http://localhost:4318/v1/traces"
+    - type: otlp
+      # Traces endpoint; metrics/logs would use /v1/metrics or /v1/logs respectively
+      endpoint: "http://localhost:4318/v1/traces"
+    # Optional: add console/file for local debugging alongside OTLP
+    # - type: console
+    # - type: file
examples/tracing/mcp/mcp_agent.config.yaml (1)

20-22: Consider including a console exporter in the example for easier local debugging.

Keeps OTLP for Jaeger while providing immediate on-stdout visibility.

Apply this diff:

 otel:
   enabled: true
   exporters:
+    - type: console
     - type: otlp
       endpoint: "http://localhost:4318/v1/traces"
examples/workflows/workflow_router/mcp_agent.config.yaml (1)

24-28: Showcase multiple exporters (incl. file and multiple OTLP) since that’s the PR goal.

Add an example illustrating file plus two OTLP collectors and optional headers/compression.

Apply:

   exporters:
-    - type: console
-    # To export to a collector, also include:
-    # - type: otlp
-    #   endpoint: "http://localhost:4318/v1/traces"
+    - type: console
+    - type: file
+      # path_settings are optional; defaults apply if omitted
+      # path: "logs/traces-{timestamp}.jsonl"
+    # Multiple collectors are supported:
+    - type: otlp
+      endpoint: "http://localhost:4318/v1/traces"
+      # headers:
+      #   Authorization: "Bearer <token>"
+      # compression: "gzip"
+    - type: otlp
+      endpoint: "https://internal-collector.example.com/v1/traces"
examples/tracing/temporal/mcp_agent.config.yaml (1)

48-51: Consider adding explicit file exporter path to avoid ambiguity.

Helps users find the trace file without digging into defaults.

Apply:

   exporters:
-    - type: file
+    - type: file
+      # path: "logs/otel-traces-{timestamp}.jsonl"
     - type: otlp
       endpoint: "http://localhost:4318/v1/traces"
examples/tracing/llm/README.md (1)

13-23: Add headers note for secured collectors.

Many setups require auth; add a commented example line.

Apply:

     - type: otlp
       endpoint: "http://localhost:4318/v1/traces"
+      # headers:
+      #   Authorization: "Bearer <token>"
examples/workflows/workflow_evaluator_optimizer/mcp_agent.config.yaml (1)

29-33: Include a file exporter example and highlight multi-export.

Aligns example with “multiple exporters” capability.

Apply:

   exporters:
-    - type: console
-    # To export to a collector, also include:
-    # - type: otlp
-    #   endpoint: "http://localhost:4318/v1/traces"
+    - type: console
+    - type: file
+      # path: "logs/traces-{timestamp}.jsonl"
+    # To export to a collector, also include one or more:
+    # - type: otlp
+    #   endpoint: "http://localhost:4318/v1/traces"
examples/workflows/workflow_orchestrator_worker/mcp_agent.config.yaml (1)

29-33: Mirror the multi-exporter guidance here too.

Add file exporter and clarify multiple OTLP endpoints option.

Apply:

   exporters:
-    - type: console
-    # To export to a collector, also include:
-    # - type: otlp
-    #   endpoint: "http://localhost:4318/v1/traces"
+    - type: console
+    - type: file
+      # path: "logs/traces-{timestamp}.jsonl"
+    # To export to one or more collectors, also include:
+    # - type: otlp
+    #   endpoint: "http://localhost:4318/v1/traces"
examples/tracing/langfuse/mcp_agent.config.yaml (1)

29-33: OTLP endpoint shape and headers: verify schema compatibility.

Ensure the new typed exporter expects a full OTLP/HTTP traces URL (with /v1/traces) and not just a host:port; otherwise requests may be double-suffixed or 404. Also confirm that headers from secrets merge correctly for Authorization.

Apply if supported by schema to make intent explicit:

 otel:
   enabled: true
   exporters:
     - type: otlp
-      endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces"
+      endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces"
+      # Optional:
+      # protocol: http/protobuf
+      # compression: gzip
+      # timeout_ms: 10000
       # Set Authorization header with API key in mcp_agent.secrets.yaml
examples/workflows/workflow_deep_orchestrator/mcp_agent.config.yaml (1)

27-35: File exporter path: ensure directory exists or is auto-created.

If the runtime doesn’t auto-create trace directories, consider adding a note or switching to a path under logs/ for consistency with logger.path_settings.

Apply if supported:

     - type: file
       path_settings:
-        path_pattern: "traces/mcp-agent-trace-{unique_id}.jsonl"
+        path_pattern: "traces/mcp-agent-trace-{unique_id}.jsonl"  # Ensure 'traces/' exists or will be created
         unique_id: "timestamp"
         timestamp_format: "%Y%m%d_%H%M%S"
     # To export to a collector as well, include:
     # - type: otlp
     #   endpoint: "http://localhost:4318/v1/traces"
examples/tracing/llm/mcp_agent.config.yaml (1)

29-34: Nice—typed exporters. Consider adding a file path example for discoverability.

The file exporter relies on defaults; a short hint helps users find traces.

   exporters:
     - type: console
-    - type: file
+    - type: file
+      # path_settings:
+      #   path_pattern: "traces/mcp-agent-trace-{unique_id}.jsonl"
+      #   unique_id: "timestamp"
+      #   timestamp_format: "%Y%m%d_%H%M%S"
examples/tracing/agent/mcp_agent.config.yaml (1)

29-34: Typed exporters look good. Add commented OTLP headers hint?

Since many collectors require auth or custom headers, a commented example can reduce misconfig.

   exporters:
     - type: console
     - type: file
     # To export to a collector, also include:
     # - type: otlp
-    #   endpoint: "http://localhost:4318/v1/traces"
+    #   endpoint: "http://localhost:4318/v1/traces"
+    #   # headers are merged from secrets; example:
+    #   # headers:
+    #   #   Authorization: "Bearer ${OTEL_TOKEN}"
src/mcp_agent/mcp/mcp_aggregator.py (5)

349-365: allowed_tools access may break after config changes.

PR summary notes removal of MCPServerSettings.allowed_tools. Directly accessing .allowed_tools could raise AttributeError at runtime.

Defensive read and set normalization:

-                else:
-                    allowed_tools = self.context.server_registry.get_server_config(
-                        server_name
-                    ).allowed_tools
+                else:
+                    cfg = self.context.server_registry.get_server_config(server_name)
+                    allowed_tools = getattr(cfg, "allowed_tools", None)
+                    if isinstance(allowed_tools, list):
+                        allowed_tools = set(allowed_tools)

And update membership test stays O(1).

Also applies to: 371-378


41-43: Global logger is mutated per instance; make it instance-scoped.

Overwriting the module-level logger in init can cause cross-instance interference. Prefer self.logger.

-logger = get_logger(
-    __name__
-)  # This will be replaced per-instance when agent_name is available
+logger = get_logger(__name__)
@@
-        global logger
-        logger_name = f"{__name__}.{name}" if name else __name__
-        logger = get_logger(logger_name)
+        logger_name = f"{__name__}.{name}" if name else __name__
+        self.logger = get_logger(logger_name)

Follow-up: swap logger. calls in methods to self.logger. (can be done incrementally).

Also applies to: 127-131


552-555: Typo in span name (“get_capabilitites”).

Minor naming nit; fix for searchability.

-        with tracer.start_as_current_span(
-            f"{self.__class__.__name__}.get_capabilitites"
-        ) as span:
+        with tracer.start_as_current_span(
+            f"{self.__class__.__name__}.get_capabilities"
+        ) as span:

310-317: Redundant reload in create().

__aenter__() already calls initialize() which loads servers. The extra load_servers() immediately after is a no-op and adds noise.

             try:
                 await instance.__aenter__()
-
-                logger.debug("Loading servers...")
-                await instance.load_servers()
-
                 logger.debug("MCPAggregator created and initialized.")
                 return instance

646-648: Docs say “dot-namespaced” but SEP is “_”.

Either update docs to “underscore-namespaced” or change SEP if dot is intended.

-        :return: Tools from all servers aggregated, and renamed to be dot-namespaced by server name.
+        :return: Tools from all servers aggregated, and renamed to be underscore-namespaced by server name.
examples/tracing/langfuse/README.md (2)

49-56: Confirm secrets path for headers after removing otlp_settings from config.

Doc shows headers under otel.otlp_settings.headers in secrets while config now uses typed exporters. Verify the loader still reads this secrets path and merges into the new OTLP exporter; if not, update the path or include an example of setting headers directly on the exporter (and document precedence).


58-66: Show multi-exporter usage and EU endpoint to reflect the PR’s goal.

Consider expanding the snippet to demonstrate multiple exporters (console + OTLP, optionally file) and include an EU endpoint example.

 otel:
   enabled: true
   exporters:
-    - type: otlp
-      endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces"
+    - type: console
+    - type: otlp
+      endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces"
+    # - type: otlp
+    #   endpoint: "https://eu.cloud.langfuse.com/api/public/otel/v1/traces"
+    # - type: file
+    #   path: "./traces.jsonl"
examples/workflows/workflow_intent_classifier/mcp_agent.config.yaml (1)

24-29: Typed exporters LGTM.

Matches the new schema and unlocks multiple exporters. Optionally add a commented example for file and OTLP to guide users.

 otel:
   enabled: false
   exporters:
     - type: console
+    # - type: file
+    #   path: "./traces.jsonl"
     # To export to a collector, also include:
     # - type: otlp
     #   endpoint: "http://localhost:4318/v1/traces"
examples/workflows/workflow_parallel/mcp_agent.config.yaml (1)

28-33: Consistent migration to typed exporters.

Looks good. Consider mirroring a multi-exporter example here as well for parity with other examples.

tests/mcp/test_mcp_aggregator.py (4)

873-876: Type the mock config for clarity.

Annotate allowed_tools to make the intent explicit.

-class MockServerConfig:
+class MockServerConfig:
     """Mock server configuration for testing"""
-    def __init__(self, allowed_tools=None):
-        self.allowed_tools = allowed_tools
+    def __init__(self, allowed_tools: "set[str] | None" = None):
+        self.allowed_tools = allowed_tools

879-913: Solid dummy registry; minor typing nits.

Add return type annotations on helpers to aid IDEs and future refactors.

-            def get_server_config(self, server_name):
+            def get_server_config(self, server_name: str) -> MockServerConfig:
                 return self.configs.get(server_name, MockServerConfig())
-            def start_server(self, server_name, client_session_factory=None):
+            def start_server(self, server_name: str, client_session_factory=None):
                 class DummyCtxMgr:

1033-1071: Assert the warning when allowed_tools is an empty set.

Capture and assert the log to prevent regressions.

-async def test_tool_filtering_empty_allowed_tools():
+async def test_tool_filtering_empty_allowed_tools(caplog):
     ...
-    await aggregator.load_server("test_server")
+    with caplog.at_level("WARNING"):
+        await aggregator.load_server("test_server")
+        assert any(
+            "Allowed tool list is explicitly empty" in r.message for r in caplog.records
+        )

1077-1114: Also assert fallback warning when no server registry is present.

-async def test_tool_filtering_no_server_registry():
+async def test_tool_filtering_no_server_registry(caplog):
     ...
-    await aggregator.load_server("test_server")
+    with caplog.at_level("WARNING"):
+        await aggregator.load_server("test_server")
+        assert any(
+            "no tool filter will be applied" in r.message.lower() for r in caplog.records
+        )
src/mcp_agent/tracing/tracer.py (1)

119-147: OTLP exporter merge logic is sound.

Merges endpoint/headers from typed config with deprecated otlp_settings. Consider optionally falling back to OTEL_EXPORTER_OTLP_ENDPOINT/HEADERS env vars in a follow-up.

src/mcp_agent/config.py (2)

513-521: Use default_factory for mutable exporters field.

Avoids sharing a mutable list across instances.

-    exporters: List[OpenTelemetryExporterSettings] = []
+    exporters: List[OpenTelemetryExporterSettings] = Field(default_factory=list)

536-586: Backward-compat coercion is solid; add a warning for unknown strings.

Unknown exporter literals are silently dropped. A warning helps users spot typos.

         for name in exporters:
             if name == "console":
                 typed_exporters.append({"type": "console"})
             elif name == "file":
                 typed_exporters.append(
                     {
                         "type": "file",
                         "path": legacy_path,
                         "path_settings": legacy_path_settings,
                     }
                 )
             elif name == "otlp":
                 typed_exporters.append(
                     {
                         "type": "otlp",
                         "endpoint": (legacy_otlp or {}).get("endpoint"),
                         "headers": (legacy_otlp or {}).get("headers"),
                     }
                 )
+            else:
+                warnings.warn(
+                    f"Unknown exporter '{name}' in 'otel.exporters'; ignoring.",
+                    stacklevel=2,
+                )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 182947f and 1ddc6e5.

📒 Files selected for processing (25)
  • examples/tracing/agent/README.md (1 hunks)
  • examples/tracing/agent/mcp_agent.config.yaml (1 hunks)
  • examples/tracing/langfuse/README.md (3 hunks)
  • examples/tracing/langfuse/mcp_agent.config.yaml (1 hunks)
  • examples/tracing/langfuse/mcp_agent.secrets.yaml.example (1 hunks)
  • examples/tracing/llm/README.md (1 hunks)
  • examples/tracing/llm/mcp_agent.config.yaml (1 hunks)
  • examples/tracing/mcp/README.md (1 hunks)
  • examples/tracing/mcp/mcp_agent.config.yaml (1 hunks)
  • examples/tracing/temporal/README.md (1 hunks)
  • examples/tracing/temporal/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_deep_orchestrator/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_evaluator_optimizer/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_intent_classifier/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_orchestrator_worker/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_parallel/mcp_agent.config.yaml (1 hunks)
  • examples/workflows/workflow_router/mcp_agent.config.yaml (1 hunks)
  • schema/mcp-agent.config.schema.json (5 hunks)
  • src/mcp_agent/cli/cloud/commands/auth/whoami/main.py (0 hunks)
  • src/mcp_agent/cli/cloud/main.py (1 hunks)
  • src/mcp_agent/config.py (3 hunks)
  • src/mcp_agent/mcp/mcp_aggregator.py (1 hunks)
  • src/mcp_agent/tracing/tracer.py (2 hunks)
  • tests/mcp/test_mcp_aggregator.py (7 hunks)
  • tests/test_tracing_isolation.py (4 hunks)
💤 Files with no reviewable changes (1)
  • src/mcp_agent/cli/cloud/commands/auth/whoami/main.py
🧰 Additional context used
🧬 Code graph analysis (4)
src/mcp_agent/mcp/mcp_aggregator.py (3)
src/mcp_agent/app.py (3)
  • context (145-150)
  • server_registry (157-158)
  • logger (189-204)
src/mcp_agent/logging/logger.py (2)
  • warning (156-164)
  • debug (136-144)
tests/mcp/test_mcp_aggregator.py (1)
  • get_server_config (890-891)
src/mcp_agent/tracing/tracer.py (3)
src/mcp_agent/app.py (2)
  • logger (189-204)
  • session_id (185-186)
src/mcp_agent/logging/logger.py (1)
  • error (166-174)
src/mcp_agent/tracing/file_span_exporter.py (1)
  • FileSpanExporter (16-73)
tests/test_tracing_isolation.py (1)
src/mcp_agent/config.py (3)
  • Settings (664-771)
  • OpenTelemetrySettings (506-585)
  • FileExporterSettings (484-487)
tests/mcp/test_mcp_aggregator.py (1)
src/mcp_agent/mcp/mcp_aggregator.py (2)
  • initialize (150-196)
  • load_server (329-458)
🔇 Additional comments (22)
examples/tracing/temporal/README.md (1)

52-58: Confirm endpoint path expectation (/v1/traces vs base URL).

Does your exporter builder expect a base endpoint (http://localhost:4318) and append /v1/traces automatically, or a full path? Mismatch can cause 404s or double paths.

If base URL is expected, apply:

-      endpoint: "http://localhost:4318/v1/traces"
+      endpoint: "http://localhost:4318"
src/mcp_agent/cli/cloud/main.py (1)

16-22: LGTM on import formatting.

Multiline import improves readability and aligns with common style tools. No functional changes.

examples/workflows/workflow_router/mcp_agent.config.yaml (2)

24-28: Typed exporters migration looks good.

Console exporter syntax matches the new discriminated union.


24-28: Confirm OTLP exporter endpoint path
Verify if the endpoint field should include the /v1/traces suffix or only the base URL, and update this example to align with the schema’s handling.

examples/tracing/temporal/mcp_agent.config.yaml (1)

48-51: Good migration to typed exporters.

Order and syntax look correct; OTLP endpoint provided.

examples/tracing/llm/README.md (1)

13-23: Clearer, copy-pastable OTEL config.

Good swap to a full YAML snippet with typed exporters.

examples/workflows/workflow_evaluator_optimizer/mcp_agent.config.yaml (1)

29-33: Typed exporters change looks correct.

Matches the new schema; console exporter retained.

examples/workflows/workflow_orchestrator_worker/mcp_agent.config.yaml (1)

29-33: Migration to typed exporters is consistent.

Console exporter format aligns with the new config model.

examples/tracing/langfuse/README.md (1)

3-3: Good shift to typed exporter language.

Clear and consistent with the repo-wide migration.

tests/mcp/test_mcp_aggregator.py (4)

919-977: Filtering test covers exact include/exclude and namespacing.

Good assertions on both per-server map and namespaced map.


983-1018: No-filter path validated well.

Coverage looks good.


1120-1220: Multi-server matrix test is comprehensive.

Nice coverage of include lists and namespacing across servers.


1223-1277: Edge-case exact-match test is on point.

Catches common substring pitfalls.

tests/test_tracing_isolation.py (2)

9-9: Importing FileExporterSettings is correct and aligns with the new typed exporter API.


296-302: LGTM on migrating to typed file exporters in tests.

Passing FileExporterSettings with explicit path/path_settings matches the new discriminated-union config and keeps tests isolated per app.

Also applies to: 304-310, 377-383, 451-457

src/mcp_agent/tracing/tracer.py (3)

95-104: Sampler wiring looks good.

Clamping sample_rate and using ParentBased(TraceIdRatioBased(...)) is correct and honors parent sampling decisions.


106-113: Robust handling of legacy vs typed exporter entries.

Gracefully supports strings and typed configs; unknown types fall through to the error path below.


151-171: File exporter configuration is correct.

custom_path and path_settings are forwarded to FileSpanExporter; per-app session_id ensures isolation.

schema/mcp-agent.config.schema.json (3)

305-353: Exporter schema definitions (console/file) look correct and minimal.

Using const type fields and allowing optional path/path_settings is aligned with the runtime model.


798-837: OTLPExporterSettings schema is well-formed.

type discriminator, endpoint, and headers match tracer usage.


931-949: Discriminated union for otel.exporters is correct.

mapping and oneOf cover console, file, and otlp.

src/mcp_agent/config.py (1)

480-488: Typed exporter classes are defined cleanly and match the schema.

No issues spotted with type literals and fields.

Comment on lines 114 to 118
tracer_provider.add_span_processor(
BatchSpanProcessor(
ConsoleSpanExporter(service_name=settings.service_name)
)
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

ConsoleSpanExporter called with an invalid argument (will raise TypeError).

ConsoleSpanExporter doesn't accept service_name. This will break when exporters include "console".

Apply this fix:

-                    BatchSpanProcessor(
-                        ConsoleSpanExporter(service_name=settings.service_name)
-                    )
+                    BatchSpanProcessor(ConsoleSpanExporter())
📝 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
tracer_provider.add_span_processor(
BatchSpanProcessor(
ConsoleSpanExporter(service_name=settings.service_name)
)
)
tracer_provider.add_span_processor(
BatchSpanProcessor(ConsoleSpanExporter())
)
🤖 Prompt for AI Agents
In src/mcp_agent/tracing/tracer.py around lines 114 to 118, the code passes
service_name to ConsoleSpanExporter which does not accept that argument and will
raise a TypeError; remove the service_name argument from ConsoleSpanExporter and
instead ensure the TracerProvider is created/updated with a Resource that sets
"service.name" to settings.service_name (i.e., construct or merge a Resource
containing {"service.name": settings.service_name} and supply it to the
TracerProvider before adding the BatchSpanProcessor using ConsoleSpanExporter()
with no args).

}
],
"default": null
"description": "Deprecated single OTLP settings. Prefer exporters list with type \"otlp\"."
Copy link
Member

Choose a reason for hiding this comment

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

What's the intention for supporting the deprecated settings vs just enforcing the new ones going forward?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Deprecating for one release to give people time to migrate, and then will remove in a subsequent change

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just best practice for a package. Can remove now as well, but it's relatively lightweight to support back-compat right now

@@ -105,10 +112,6 @@ class MCPServerSettings(BaseModel):
env: Dict[str, str] | None = None
"""Environment variables to pass to the server process."""

allowed_tools: Set[str] | None = None
Copy link
Member

Choose a reason for hiding this comment

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

Intentional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oof good catch thanks!


class OTLPExporterSettings(OpenTelemetryExporterBase):
type: Literal["otlp"] = "otlp"
endpoint: str | None = None
Copy link
Member

Choose a reason for hiding this comment

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

Should this be required str?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, yes

@@ -7,6 +7,7 @@ anthropic:
api_key: anthropic_api_key

otel:
# Headers are merged with typed OTLP exporter settings
Copy link
Member

Choose a reason for hiding this comment

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

How would this look for the new format? Would we need to duplicate the -type and endpoint here to map to the correct one (in case of multiple)?

allowed_tools = self.context.server_registry.get_server_config(server_name).allowed_tools
allowed_tools = self.context.server_registry.get_server_config(
server_name
).allowed_tools
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this removed above now?

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.

2 participants