Skip to content
This repository was archived by the owner on Oct 28, 2025. It is now read-only.

Commit f96dba9

Browse files
committed
link parent span
1 parent da3dbce commit f96dba9

File tree

3 files changed

+43
-38
lines changed

3 files changed

+43
-38
lines changed

src/semgrep_mcp/semgrep.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from mcp.shared.exceptions import McpError
88
from mcp.types import INTERNAL_ERROR, ErrorData
9+
from opentelemetry import trace
910

1011
from semgrep_mcp.models import CodeFile
1112
from semgrep_mcp.semgrep_interfaces.semgrep_output_v1 import CliOutput
@@ -139,9 +140,11 @@ class SemgrepContext:
139140
process: asyncio.subprocess.Process
140141
stdin: asyncio.StreamWriter
141142
stdout: asyncio.StreamReader
143+
top_level_span: trace.Span
142144

143-
def __init__(self, process: asyncio.subprocess.Process) -> None:
145+
def __init__(self, process: asyncio.subprocess.Process, top_level_span: trace.Span) -> None:
144146
self.process = process
147+
self.top_level_span = top_level_span
145148

146149
if process.stdin is not None and process.stdout is not None:
147150
self.stdin = process.stdin

src/semgrep_mcp/server.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
set_semgrep_executable,
3131
)
3232
from semgrep_mcp.semgrep_interfaces.semgrep_output_v1 import CliOutput
33-
from utilities.tracing import initialize_tracing
33+
from utilities.tracing import start_tracing, with_span
3434

3535
# ---------------------------------------------------------------------------------
3636
# Constants
@@ -282,18 +282,18 @@ def remove_temp_dir_from_results(results: SemgrepScanResult, temp_dir: str) -> N
282282
async def server_lifespan(_server: Server) -> AsyncIterator[SemgrepContext]:
283283
"""Manage server startup and shutdown lifecycle."""
284284
# Initialize resources on startup with tracing
285-
# MCP requires Pro Engine
286-
with initialize_tracing("mcp-python-server") as span:
285+
# MCP requires Pro Engine
286+
with start_tracing("mcp-python-server") as span:
287287
process = await run_semgrep_process(["mcp", "--pro", "--debug"])
288288

289-
try:
290-
yield SemgrepContext(process=process)
291-
finally:
292-
if process.returncode is None:
293-
# Clean up on shutdown
294-
process.terminate()
295-
else:
296-
print(f"`semgrep mcp` process exited with code {process.returncode} already")
289+
try:
290+
yield SemgrepContext(process=process, top_level_span=span)
291+
finally:
292+
if process.returncode is None:
293+
# Clean up on shutdown
294+
process.terminate()
295+
else:
296+
print(f"`semgrep mcp` process exited with code {process.returncode} already")
297297

298298

299299
# Create a fast MCP server
@@ -678,7 +678,8 @@ async def semgrep_scan_rpc(
678678
temp_dir = None
679679
try:
680680
# TODO: perhaps should return more interpretable results?
681-
cli_output = await run_semgrep_via_rpc(context, code_files)
681+
with with_span(context.top_level_span, "semgrep_scan_rpc"):
682+
cli_output = await run_semgrep_via_rpc(context, code_files)
682683
return cli_output
683684
except McpError as e:
684685
raise e

src/utilities/tracing.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env python3
22

33
import os
4+
from collections.abc import Generator
45
from contextlib import contextmanager
5-
from typing import Generator
66

77
from opentelemetry import trace
88
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
9-
from opentelemetry.sdk.resources import SERVICE_NAME, DEPLOYMENT_ENVIRONMENT, Resource
9+
from opentelemetry.sdk.resources import DEPLOYMENT_ENVIRONMENT, SERVICE_NAME, Resource
1010
from opentelemetry.sdk.trace import TracerProvider
1111
from opentelemetry.sdk.trace.export import BatchSpanProcessor
1212

@@ -17,62 +17,63 @@
1717

1818
MCP_SERVICE_NAME = "mcp"
1919

20-
top_level_span : trace.Span | None = None
2120

22-
23-
def get_trace_endpoint() -> (str, str):
21+
def get_trace_endpoint() -> tuple[str, str]:
2422
"""Get the appropriate trace endpoint based on environment."""
2523
env = os.environ.get("ENVIRONMENT", "dev").lower()
26-
24+
2725
if env == "prod":
2826
return (DEFAULT_TRACE_ENDPOINT, "prod")
2927
elif env == "local":
3028
return (DEFAULT_LOCAL_ENDPOINT, "local")
3129
else:
3230
return (DEFAULT_DEV_ENDPOINT, "dev")
3331

32+
3433
@contextmanager
35-
def initialize_tracing(name: str) -> Generator[trace.Span, None, None]:
34+
def start_tracing(name: str) -> Generator[trace.Span, None, None]:
3635
"""Initialize OpenTelemetry tracing."""
37-
3836
(endpoint, env) = get_trace_endpoint()
39-
37+
4038
# Create resource with basic attributes
41-
resource = Resource.create({
42-
SERVICE_NAME: MCP_SERVICE_NAME,
43-
DEPLOYMENT_ENVIRONMENT: env,
44-
})
39+
resource = Resource.create(
40+
{
41+
SERVICE_NAME: MCP_SERVICE_NAME,
42+
DEPLOYMENT_ENVIRONMENT: env,
43+
}
44+
)
4545
# Create tracer provider
4646
provider = TracerProvider(resource=resource)
47-
47+
4848
# Create OTLP exporter
4949
exporter = OTLPSpanExporter(endpoint=endpoint)
50-
50+
5151
# Create span processor
5252
processor = BatchSpanProcessor(exporter)
5353
provider.add_span_processor(processor)
54-
54+
5555
# Set the global tracer provider
5656
trace.set_tracer_provider(provider)
57-
57+
5858
# Get tracer instance
5959
tracer = trace.get_tracer(MCP_SERVICE_NAME)
6060

6161
with tracer.start_as_current_span(name) as span:
62-
top_level_span = span
63-
trace_id = trace.format_trace_id(top_level_span.get_span_context().trace_id)
62+
trace_id = trace.format_trace_id(span.get_span_context().trace_id)
6463
# TODO: use logging
6564
print("Tracing initialized")
6665
print(f"Tracing initialized with trace ID: {trace_id}")
67-
66+
6867
yield span
6968

7069

7170
@contextmanager
72-
def trace_span(
73-
name: str,
71+
def with_span(
72+
parent_span: trace.Span,
73+
name: str,
7474
) -> Generator[trace.Span, None, None]:
7575
tracer = trace.get_tracer(MCP_SERVICE_NAME)
76-
77-
with tracer.start_as_current_span(name) as span:
78-
yield span
76+
77+
context = trace.set_span_in_context(parent_span)
78+
with tracer.start_span(name, context=context) as span:
79+
yield span

0 commit comments

Comments
 (0)