Skip to content

Commit 0cefcaa

Browse files
Merge branch 'main' into feature/es-datastream-support
2 parents edfa85a + 0954788 commit 0cefcaa

File tree

112 files changed

+6232
-2847
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+6232
-2847
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ cmd/agent/agent
2525
cmd/agent/agent-*
2626
cmd/anonymizer/anonymizer
2727
cmd/anonymizer/anonymizer-*
28+
cmd/jaeger/jaeger
29+
cmd/jaeger/jaeger-*
2830
cmd/jaeger/internal/integration/results
2931
cmd/remote-storage/remote-storage
3032
cmd/remote-storage/remote-storage-*

CONTRIBUTING.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,13 @@ We are currently using `gofumpt`, which is installed automatically by `make inst
7272
### Running local build with the UI
7373

7474
```
75-
$ make run-all-in-one
75+
$ go run ./cmd/jaeger --config ./cmd/jaeger/config.yaml
7676
```
7777

7878
#### What does this command do?
7979

80-
The `jaeger-ui` submodule, which was added from the Pre-requisites step above, contains
81-
the source code for the UI assets (requires Node.js 6+).
82-
83-
The assets must be compiled first with `make build-ui`, which runs Node.js build and then
84-
packages the assets into a Go file that is `.gitignore`-ed.
85-
86-
`make run-all-in-one` essentially runs Jaeger all-in-one by combining both of the above
87-
steps into a single `make` command.
80+
The Jaeger binary runs with the default configuration file (config.yaml) that includes
81+
the UI configuration via the `jaeger_query` extension. The `jaeger-ui` submodule, which was added from the Pre-requisites step above, contains the source code for the UI assets (requires Node.js 24+). The assets must be compiled first with `make build-ui`, which normally downloads them from the latest UI release, but can also build them from source.
8882

8983
## Project Structure
9084

cmd/jaeger/config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
service:
2-
extensions: [jaeger_storage, jaeger_query, remote_sampling, healthcheckv2, pprof]
2+
extensions: [jaeger_storage, jaeger_query, jaeger_mcp, remote_sampling, healthcheckv2, pprof]
33
pipelines:
44
traces:
55
receivers: [otlp, jaeger, zipkin]
@@ -41,6 +41,12 @@ extensions:
4141
# Defaults to 0 seconds, which means it's disabled.
4242
max_clock_skew_adjust: 0s
4343

44+
jaeger_mcp:
45+
# MCP (Model Context Protocol) server for LLM integration
46+
# Depends on jaeger_query extension
47+
http:
48+
endpoint: 0.0.0.0:16687
49+
4450
jaeger_storage:
4551
backends:
4652
some_store:

cmd/jaeger/internal/all-in-one.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
service:
2-
extensions: [jaeger_storage, jaeger_query, remote_sampling, healthcheckv2, expvar, zpages]
2+
extensions: [jaeger_storage, jaeger_query, jaeger_mcp, remote_sampling, healthcheckv2, expvar, zpages]
33
pipelines:
44
traces:
55
receivers: [otlp, jaeger, zipkin]
@@ -26,6 +26,12 @@ extensions:
2626
storage:
2727
traces: some_storage
2828

29+
jaeger_mcp:
30+
# MCP (Model Context Protocol) server for LLM integration
31+
# Depends on jaeger_query extension
32+
http:
33+
endpoint: "${env:JAEGER_LISTEN_HOST:-localhost}:16687"
34+
2935
jaeger_storage:
3036
backends:
3137
some_storage:

cmd/jaeger/internal/components.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838

3939
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/exporters/storageexporter"
4040
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/expvar"
41+
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegermcp"
4142
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerquery"
4243
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage"
4344
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/remotesampling"
@@ -79,6 +80,7 @@ func (b builders) build() (otelcol.Factories, error) {
7980
// add-ons
8081
basicauthextension.NewFactory(),
8182
sigv4authextension.NewFactory(),
83+
jaegermcp.NewFactory(),
8284
jaegerquery.NewFactory(),
8385
jaegerstorage.NewFactory(),
8486
remotesampling.NewFactory(),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# jaeger_mcp
2+
3+
This extension implements a Model Context Protocol (MCP) server for Jaeger, enabling LLM-based assistants to query and analyze distributed traces efficiently.
4+
5+
## Overview
6+
7+
The MCP server provides a structured way for AI agents to interact with Jaeger's trace data using progressive disclosure:
8+
- **Search** → Find traces matching specific criteria
9+
- **Map** → Visualize trace structure without loading full attribute data
10+
- **Diagnose** → Identify critical execution paths that contributed to latency or errors
11+
- **Inspect** → Load full details only for specific, suspicious spans
12+
13+
This approach prevents context-window exhaustion in LLMs and enables more efficient trace analysis.
14+
15+
**Note:** The current implementation uses Streamable HTTP transport only. MCP stdio transport is not supported.
16+
17+
## Status
18+
19+
**Phase 1: Foundation (Complete)** - Extension scaffold, lifecycle management, and MCP SDK integration
20+
21+
Future phases will add:
22+
- Phase 2: Basic MCP tools (search, span details, errors)
23+
- Phase 3: Advanced tools (topology, critical path)
24+
- Phase 4: Documentation and observability
25+
26+
See [ADR-002](../../../../docs/adr/002-mcp-server.md) for full design details.
27+
28+
## Configuration
29+
30+
```yaml
31+
extensions:
32+
jaeger_mcp:
33+
# HTTP endpoint for MCP protocol (Streamable HTTP transport)
34+
http:
35+
endpoint: "0.0.0.0:16687"
36+
37+
# Server identification for MCP protocol
38+
server_name: "jaeger"
39+
# server_version will default to the build version
40+
41+
# Limits
42+
max_span_details_per_request: 20
43+
max_search_results: 100
44+
```
45+
46+
## Dependencies
47+
48+
This extension depends on the [jaeger_query](../jaegerquery/) extension to access trace data. The `jaeger_query` extension must be configured in the service extensions list.
49+
50+
## Development Status
51+
52+
Phase 1 implements:
53+
- ✅ Extension directory structure
54+
- ✅ Configuration validation
55+
- ✅ Factory implementation
56+
- ✅ Server lifecycle management
57+
- ✅ MCP SDK integration
58+
- ✅ Streamable HTTP transport
59+
- ✅ Basic health tool (placeholder for Phase 2)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2026 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jaegermcp
5+
6+
import (
7+
"github.com/asaskevich/govalidator"
8+
"go.opentelemetry.io/collector/config/confighttp"
9+
"go.opentelemetry.io/collector/confmap/xconfmap"
10+
)
11+
12+
// Config represents the configuration for the Jaeger MCP server extension.
13+
type Config struct {
14+
// HTTP contains the HTTP server configuration for the MCP protocol endpoint.
15+
HTTP confighttp.ServerConfig `mapstructure:"http"`
16+
17+
// ServerName is the name of the MCP server for protocol identification.
18+
ServerName string `mapstructure:"server_name"`
19+
20+
// ServerVersion is the version of the MCP server.
21+
ServerVersion string `mapstructure:"server_version" valid:"required"`
22+
23+
// MaxSpanDetailsPerRequest limits the number of spans that can be fetched in a single request.
24+
MaxSpanDetailsPerRequest int `mapstructure:"max_span_details_per_request" valid:"range(1|100)"`
25+
26+
// MaxSearchResults limits the number of trace search results.
27+
MaxSearchResults int `mapstructure:"max_search_results" valid:"range(1|1000)"`
28+
}
29+
30+
// Validate checks if the configuration is valid.
31+
func (cfg *Config) Validate() error {
32+
// if cfg.ServerVersion == "" {
33+
// cfg.ServerVersion = version.Get().GitVersion
34+
// }
35+
_, err := govalidator.ValidateStruct(cfg)
36+
return err
37+
}
38+
39+
var _ xconfmap.Validator = (*Config)(nil)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2026 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jaegermcp
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestValidate(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
config *Config
16+
expectError bool
17+
}{
18+
{
19+
name: "Valid config with ServerVersion",
20+
config: &Config{
21+
ServerVersion: "1.0.0",
22+
MaxSpanDetailsPerRequest: 20,
23+
MaxSearchResults: 100,
24+
},
25+
expectError: false,
26+
},
27+
{
28+
name: "Invalid MaxSpanDetailsPerRequest (too high)",
29+
config: &Config{
30+
ServerVersion: "1.0.0",
31+
MaxSpanDetailsPerRequest: 101,
32+
MaxSearchResults: 100,
33+
},
34+
expectError: true,
35+
},
36+
{
37+
name: "Invalid MaxSearchResults (too high)",
38+
config: &Config{
39+
ServerVersion: "1.0.0",
40+
MaxSpanDetailsPerRequest: 20,
41+
MaxSearchResults: 1001,
42+
},
43+
expectError: true,
44+
},
45+
}
46+
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
err := tt.config.Validate()
50+
if tt.expectError {
51+
require.Error(t, err)
52+
} else {
53+
require.NoError(t, err)
54+
}
55+
})
56+
}
57+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) 2026 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jaegermcp
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/collector/component"
10+
"go.opentelemetry.io/collector/config/confighttp"
11+
"go.opentelemetry.io/collector/extension"
12+
13+
"github.com/jaegertracing/jaeger/internal/version"
14+
"github.com/jaegertracing/jaeger/ports"
15+
)
16+
17+
// componentType is the name of this extension in configuration.
18+
var componentType = component.MustNewType("jaeger_mcp")
19+
20+
// ID is the identifier of this extension.
21+
var ID = component.NewID(componentType)
22+
23+
// NewFactory creates a factory for the Jaeger MCP extension.
24+
func NewFactory() extension.Factory {
25+
return extension.NewFactory(
26+
componentType,
27+
createDefaultConfig,
28+
createExtension,
29+
component.StabilityLevelAlpha,
30+
)
31+
}
32+
33+
// createDefaultConfig creates the default configuration for the extension.
34+
func createDefaultConfig() component.Config {
35+
ver := version.Get().GitVersion
36+
if ver == "" {
37+
ver = "dev"
38+
}
39+
return &Config{
40+
HTTP: confighttp.ServerConfig{
41+
Endpoint: ports.PortToHostPort(ports.MCPHTTP),
42+
},
43+
ServerName: "jaeger",
44+
ServerVersion: ver,
45+
MaxSpanDetailsPerRequest: 20,
46+
MaxSearchResults: 100,
47+
}
48+
}
49+
50+
// createExtension creates the extension based on this config.
51+
func createExtension(_ context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) {
52+
return newServer(cfg.(*Config), set.TelemetrySettings), nil
53+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2026 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jaegermcp
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"go.opentelemetry.io/collector/extension"
13+
)
14+
15+
func TestNewFactory(t *testing.T) {
16+
factory := NewFactory()
17+
assert.Equal(t, componentType, factory.Type())
18+
assert.Equal(t, factory.CreateDefaultConfig(), createDefaultConfig())
19+
}
20+
21+
func TestCreateExtension(t *testing.T) {
22+
set := extension.Settings{
23+
ID: ID,
24+
}
25+
cfg := createDefaultConfig()
26+
ext, err := createExtension(context.Background(), set, cfg)
27+
28+
require.NoError(t, err)
29+
assert.NotNil(t, ext)
30+
}
31+
32+
func TestCreateDefaultConfig(t *testing.T) {
33+
cfg := createDefaultConfig()
34+
assert.NotNil(t, cfg)
35+
36+
mcpCfg, ok := cfg.(*Config)
37+
require.True(t, ok)
38+
39+
assert.Equal(t, ":16687", mcpCfg.HTTP.Endpoint)
40+
assert.Equal(t, "jaeger", mcpCfg.ServerName)
41+
// server_version will be empty in tests since it's set at build time
42+
assert.Equal(t, 20, mcpCfg.MaxSpanDetailsPerRequest)
43+
assert.Equal(t, 100, mcpCfg.MaxSearchResults)
44+
}

0 commit comments

Comments
 (0)