Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,47 @@ require (
github.com/BurntSushi/toml v1.6.0
github.com/modelcontextprotocol/go-sdk v1.4.1
github.com/spf13/cobra v1.10.2
golang.org/x/term v0.38.0
golang.org/x/term v0.41.0
)

require (
github.com/itchyny/gojq v0.12.18
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
)

require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.40.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
74 changes: 65 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc=
github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg=
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc=
github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
Expand All @@ -36,16 +57,51 @@ github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbw
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
39 changes: 39 additions & 0 deletions internal/cmd/flags_tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cmd

// Tracing-related flags for OpenTelemetry OTLP trace export.

import (
"github.com/github/gh-aw-mcpg/internal/config"
"github.com/github/gh-aw-mcpg/internal/envutil"
"github.com/spf13/cobra"
)

// Tracing flag variables
var (
otlpEndpoint string
otlpServiceName string
otlpSampleRate float64
)

func init() {
RegisterFlag(func(cmd *cobra.Command) {
cmd.Flags().StringVar(&otlpEndpoint, "otlp-endpoint", getDefaultOTLPEndpoint(),
"OTLP HTTP endpoint for trace export (e.g. http://localhost:4318). Overrides OTEL_EXPORTER_OTLP_ENDPOINT env var. Tracing is disabled when empty.")
cmd.Flags().StringVar(&otlpServiceName, "otlp-service-name", getDefaultOTLPServiceName(),
"Service name reported in traces. Overrides OTEL_SERVICE_NAME env var.")
cmd.Flags().Float64Var(&otlpSampleRate, "otlp-sample-rate", config.DefaultTracingSampleRate,
"Fraction of traces to sample and export (0.0–1.0). Default 1.0 samples everything.")
})
}

// getDefaultOTLPEndpoint returns the OTLP endpoint, checking OTEL_EXPORTER_OTLP_ENDPOINT
// environment variable first, then falling back to empty (disabled).
func getDefaultOTLPEndpoint() string {
return envutil.GetEnvString("OTEL_EXPORTER_OTLP_ENDPOINT", "")
}

// getDefaultOTLPServiceName returns the OTLP service name, checking OTEL_SERVICE_NAME
// environment variable first, then falling back to the default.
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The flag help text says --otlp-endpoint/--otlp-service-name override the corresponding OTEL env vars, but tracing.resolveEndpoint/resolveServiceName currently prefer OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_SERVICE_NAME over config/flags. This means operators cannot override (or disable) an env-configured endpoint via CLI, which is surprising and contradicts the help. Consider changing precedence to CLI > env > config (e.g., resolve envs in cmd layer and pass resolved values to InitProvider), or update the help text to match actual precedence.

Suggested change
"OTLP HTTP endpoint for trace export (e.g. http://localhost:4318). Overrides OTEL_EXPORTER_OTLP_ENDPOINT env var. Tracing is disabled when empty.")
cmd.Flags().StringVar(&otlpServiceName, "otlp-service-name", getDefaultOTLPServiceName(),
"Service name reported in traces. Overrides OTEL_SERVICE_NAME env var.")
cmd.Flags().Float64Var(&otlpSampleRate, "otlp-sample-rate", config.DefaultTracingSampleRate,
"Fraction of traces to sample and export (0.0–1.0). Default 1.0 samples everything.")
})
}
// getDefaultOTLPEndpoint returns the OTLP endpoint, checking OTEL_EXPORTER_OTLP_ENDPOINT
// environment variable first, then falling back to empty (disabled).
func getDefaultOTLPEndpoint() string {
return envutil.GetEnvString("OTEL_EXPORTER_OTLP_ENDPOINT", "")
}
// getDefaultOTLPServiceName returns the OTLP service name, checking OTEL_SERVICE_NAME
// environment variable first, then falling back to the default.
"OTLP HTTP endpoint for trace export (e.g. http://localhost:4318). Defaults from OTEL_EXPORTER_OTLP_ENDPOINT when set. Tracing is disabled when empty.")
cmd.Flags().StringVar(&otlpServiceName, "otlp-service-name", getDefaultOTLPServiceName(),
"Service name reported in traces. Defaults from OTEL_SERVICE_NAME when set.")
cmd.Flags().Float64Var(&otlpSampleRate, "otlp-sample-rate", config.DefaultTracingSampleRate,
"Fraction of traces to sample and export (0.0–1.0). Default 1.0 samples everything.")
})
}
// getDefaultOTLPEndpoint returns the default value for the OTLP endpoint flag,
// using OTEL_EXPORTER_OTLP_ENDPOINT when set and otherwise falling back to empty
// (disabled).
func getDefaultOTLPEndpoint() string {
return envutil.GetEnvString("OTEL_EXPORTER_OTLP_ENDPOINT", "")
}
// getDefaultOTLPServiceName returns the default value for the OTLP service name
// flag, using OTEL_SERVICE_NAME when set and otherwise falling back to the
// configured default.

Copilot uses AI. Check for mistakes.
func getDefaultOTLPServiceName() string {
return envutil.GetEnvString("OTEL_SERVICE_NAME", config.DefaultTracingServiceName)
}
52 changes: 52 additions & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/github/gh-aw-mcpg/internal/difc"
"github.com/github/gh-aw-mcpg/internal/logger"
"github.com/github/gh-aw-mcpg/internal/server"
"github.com/github/gh-aw-mcpg/internal/tracing"
"github.com/github/gh-aw-mcpg/internal/version"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -312,6 +313,57 @@ func run(cmd *cobra.Command, args []string) error {
logger.LogInfoMd("startup", "Generated temporary random API key (spec §7.3)")
}

// Apply tracing flags: CLI flags override config values.
// Merge CLI/env tracing settings into gateway config.
if otlpEndpoint != "" || cmd.Flags().Changed("otlp-endpoint") {
if cfg.Gateway.Tracing == nil {
cfg.Gateway.Tracing = &config.TracingConfig{}
}
cfg.Gateway.Tracing.Endpoint = otlpEndpoint
}
if cmd.Flags().Changed("otlp-service-name") {
if cfg.Gateway.Tracing == nil {
cfg.Gateway.Tracing = &config.TracingConfig{}
}
cfg.Gateway.Tracing.ServiceName = otlpServiceName
}
if cmd.Flags().Changed("otlp-sample-rate") {
if cfg.Gateway.Tracing == nil {
cfg.Gateway.Tracing = &config.TracingConfig{}
}
cfg.Gateway.Tracing.SampleRate = otlpSampleRate
}

// Initialize OpenTelemetry tracer provider.
// When no endpoint is configured, a noop provider is used (zero overhead).
var tracingCfg *config.TracingConfig
if cfg.Gateway != nil {
tracingCfg = cfg.Gateway.Tracing
}
tracingProvider, err := tracing.InitProvider(ctx, tracingCfg)
if err != nil {
log.Printf("Warning: failed to initialize tracing provider: %v", err)
logger.LogWarn("startup", "Failed to initialize tracing provider: %v", err)
// Non-fatal: continue without tracing
tracingProvider, _ = tracing.InitProvider(ctx, nil)
}
defer func() {
shutdownCtxTracing, cancelTracing := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelTracing()
if err := tracingProvider.Shutdown(shutdownCtxTracing); err != nil {
log.Printf("Warning: tracing provider shutdown error: %v", err)
}
}()

if tracingCfg != nil && tracingCfg.Endpoint != "" {
log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f",
tracingCfg.Endpoint, tracingCfg.ServiceName, tracingCfg.SampleRate)
logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s",
tracingCfg.Endpoint, tracingCfg.ServiceName)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The tracing enable/disable log message is based on tracingCfg.Endpoint, but InitProvider resolves the endpoint from OTEL_EXPORTER_OTLP_ENDPOINT first. In particular, if OTEL_EXPORTER_OTLP_ENDPOINT is set and a user runs with --otlp-endpoint= (explicitly disabling via CLI), the log will claim tracing is disabled while InitProvider will still enable tracing via the env var. Consider aligning the precedence (CLI > env > config) or logging the resolved endpoint actually used by InitProvider.

Suggested change
if tracingCfg != nil && tracingCfg.Endpoint != "" {
log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f",
tracingCfg.Endpoint, tracingCfg.ServiceName, tracingCfg.SampleRate)
logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s",
tracingCfg.Endpoint, tracingCfg.ServiceName)
resolvedTracingEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
if resolvedTracingEndpoint == "" && tracingCfg != nil {
resolvedTracingEndpoint = tracingCfg.Endpoint
}
if resolvedTracingEndpoint != "" {
log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f",
resolvedTracingEndpoint, tracingCfg.ServiceName, tracingCfg.SampleRate)
logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s",
resolvedTracingEndpoint, tracingCfg.ServiceName)

Copilot uses AI. Check for mistakes.
} else {
log.Printf("OpenTelemetry tracing disabled (no OTLP endpoint configured)")
}

// Create unified MCP server (backend for both modes)
unifiedServer, err := server.NewUnified(ctx, cfg)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions internal/config/config_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ type GatewayConfig struct {
// bot list and is purely additive (it cannot remove built-in trusted bots).
// Example values: "copilot-swe-agent[bot]", "my-org-bot[bot]"
TrustedBots []string `toml:"trusted_bots" json:"trusted_bots,omitempty"`

// Tracing holds OpenTelemetry OTLP tracing configuration.
// When Endpoint is set, traces are exported to the specified OTLP endpoint.
// When omitted or Endpoint is empty, a noop tracer is used (zero overhead).
Tracing *TracingConfig `toml:"tracing" json:"tracing,omitempty"`
}

// HTTPKeepaliveInterval returns the keepalive interval as a time.Duration.
Expand Down
52 changes: 52 additions & 0 deletions internal/config/config_tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Package config provides configuration loading and parsing.
// This file defines the tracing configuration for OpenTelemetry OTLP export.
package config

// DefaultTracingSampleRate is the default sample rate for tracing (100% sampling).
const DefaultTracingSampleRate = 1.0

// DefaultTracingServiceName is the default service name for tracing.
const DefaultTracingServiceName = "mcp-gateway"

// TracingConfig holds OpenTelemetry tracing configuration.
// Tracing is disabled when Endpoint is empty.
//
// Configuration can also be provided via standard OTEL environment variables:
// - OTEL_EXPORTER_OTLP_ENDPOINT — overrides Endpoint
// - OTEL_SERVICE_NAME — overrides ServiceName
//
// Example TOML:
//
// [gateway.tracing]
// endpoint = "http://localhost:4318"
// service_name = "mcp-gateway"
// sample_rate = 1.0
type TracingConfig struct {
// Endpoint is the OTLP HTTP endpoint to export traces to.
// Example: "http://localhost:4318" (Jaeger, Grafana Tempo, Honeycomb, etc.)
// If empty, tracing is disabled and a noop tracer is used.
Endpoint string `toml:"endpoint" json:"endpoint,omitempty"`

// ServiceName is the service name reported in traces.
// Defaults to "mcp-gateway".
ServiceName string `toml:"service_name" json:"service_name,omitempty"`

// SampleRate controls the fraction of traces that are sampled and exported.
// Valid range: 0.0 (no sampling) to 1.0 (sample everything).
// Defaults to 1.0 (100% sampling).
SampleRate float64 `toml:"sample_rate" json:"sample_rate,omitempty"`
}

func init() {
// Register default setter for Tracing config
RegisterDefaults(func(cfg *Config) {
if cfg.Gateway != nil && cfg.Gateway.Tracing != nil {
if cfg.Gateway.Tracing.ServiceName == "" {
cfg.Gateway.Tracing.ServiceName = DefaultTracingServiceName
}
if cfg.Gateway.Tracing.SampleRate == 0 {
cfg.Gateway.Tracing.SampleRate = DefaultTracingSampleRate
}
}
Comment on lines +51 to +56
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The defaults setter forces SampleRate to 1.0 when the decoded value is 0, which prevents users from configuring sample_rate = 0.0 in TOML (documented as valid for “no sampling”). To support an explicit 0.0 value, consider representing SampleRate as a pointer (so “unset” can be distinguished from 0.0) or using a separate “set” flag during decode/defaulting.

Copilot uses AI. Check for mistakes.
})
}
Loading
Loading