Repository: googleapis/genai-toolbox
File: internal/server/mcp.go (Go)

Code before:
				Tracestate  string `json:"tracestate,omitempty"`
			} `json:"_meta,omitempty"`
		} `json:"params,omitempty"`
	}

	if err := json.Unmarshal(body, &req); err != nil {
		return ctx
	}

	// If traceparent is present, extract the context
	if req.Params.Meta.Traceparent != "" {
		carrier := traceContextCarrier{
			"traceparent": req.Params.Meta.Traceparent,
		}
		if req.Params.Meta.Tracestate != "" {
			carrier["tracestate"] = req.Params.Meta.Tracestate
		}
		return otel.GetTextMapPropagator().Extract(ctx, carrier)
	}

	return ctx
}

// getMethodSpanName returns the span name for a given MCP method following semantic conventions
// Format: {mcp.method.name} {target} (e.g., "tools/call get-weather")
func getMethodSpanName(method string, body []byte) string {
	switch method {
	case "tools/call":
		var req struct {
			Params struct {
				Name string `json:"name"`
			} `json:"params"`
		}
		if json.Unmarshal(body, &req) == nil && req.Params.Name != "" {
			return fmt.Sprintf("tools/call %s", req.Params.Name)
		}
		return "tools/call"
	case "prompts/get":
		var req struct {
			Params struct {
				Name string `json:"name"`
			} `json:"params"`
		}
		if json.Unmarshal(body, &req) == nil && req.Params.Name != "" {
			return fmt.Sprintf("prompts/get %s", req.Params.Name)
		}
		return "prompts/get"
	default:
		return method
	}
}

Review comment (bug):
In cases where this method fail to retrieve the target, it will return `{mcp.method.name}`. In this case, I'm leaning towards removing this function, and naming the span with the `{mcp.method.name}` in `processMcpMessage()` when we start the span.

Then, within each method handlers (e.g. https://github.com/googleapis/genai-toolbox/blob/a15a12873f936b0102aeb9500cc3bcd71bb38c34/internal/server/mcp/v20241105/method.go#L87), we can use `trace.SpanFromContext(ctx)` to retrieve tha ctive span -> update name (e.g. `span.SetName(fmt.Springf("%s %s", TOOLS_CALL, toolsName))`. We'll have to update this in all mcp supported mcp version here (https://github.com/googleapis/genai-toolbox/tree/main/internal/server/mcp)

p/s: in the example above, `TOOLS_CALL` is a const defined in types.go (e.g. https://github.com/googleapis/genai-toolbox/blob/a15a12873f936b0102aeb9500cc3bcd71bb38c34/internal/server/mcp/v20241105/types.go#L33)`


Code after:
			} `json:"_meta,omitempty"`
		} `json:"params,omitempty"`
	}

	if err := json.Unmarshal(body, &req); err != nil {
		return ctx
	}

	// If traceparent is present, extract the context
	if req.Params.Meta.Traceparent != "" {
		carrier := traceContextCarrier{
			"traceparent": req.Params.Meta.Traceparent,
		}
		if req.Params.Meta.Tracestate != "" {
			carrier["tracestate"] = req.Params.Meta.Tracestate
		}
		return otel.GetTextMapPropagator().Extract(ctx, carrier)
	}

	return ctx
}

func NewStdioSession(s *Server, stdin io.Reader, stdout io.Writer) *stdioSession {
	stdioSession := &stdioSession{
		server: s,
		reader: bufio.NewReader(stdin),
		writer: stdout,
	}
	return stdioSession
}

func (s *stdioSession) Start(ctx context.Context) error {
	return s.readInputStream(ctx)
}

// readInputStream reads requests/notifications from MCP clients through stdin
func (s *stdioSession) readInputStream(ctx context.Context) error {
	for {
		if err := ctx.Err(); err != nil {
			return err
		}
		line, err := s.readLine(ctx)
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		// This ensures the transport span becomes a child of the client span
		msgCtx := extractTraceContext(ctx, []byte(line))