Skip to content

Commit 6f2ea17

Browse files
Update enabling tempo mcp and docs (#1385)
Blocked by #1392 Tempo's MCP server is intentionally disabled by default in Tempo itself. Updating docs and config to start disabled, but give the user the ability to enable it instead of how the current config enables it for everyone. I added a flag for this to Tempo, which resulted in [release 2.10.4](https://github.com/grafana/tempo/releases/tag/v2.10.4). To verify it enabled it use curl: ``` curl -s http://localhost:3200/status/config | grep -A 1 "mcp" ``` ``` mcp_server: enabled: true ``` Additionally, the Grafana MCP server has tools for Pyroscope, so I removed the section saying there's no MCP server. See the [README](https://github.com/grafana/docker-otel-lgtm/blob/main/docs/mcp-integration.md#pyroscope-continuous-profiling) in the MCP server GH. --------- Co-authored-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent c059bb4 commit 6f2ea17

12 files changed

Lines changed: 437 additions & 154 deletions

File tree

.github/renovate-tracked-deps.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"cosign"
4646
]
4747
},
48-
".github/workflows/lint.yml": {
48+
".github/workflows/ci.yml": {
4949
"regex": [
5050
"mise"
5151
]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
---
2-
name: Lint
2+
name: CI
33

44
on:
55
pull_request:
66

77
permissions: {}
88

99
jobs:
10-
lint:
10+
ci:
1111
runs-on: ubuntu-24.04
1212

1313
permissions:

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ some users even in testing situations.
162162
Each backend supports a `*_EXTRA_ARGS` environment variable for passing additional
163163
CLI flags without modifying any files:
164164

165-
| Backend | Env var | Example |
166-
|-------------------------|-------------------------|--------------------------------------|
167-
| Prometheus | `PROMETHEUS_EXTRA_ARGS` | `--storage.tsdb.retention.time=90d` |
168-
| Loki | `LOKI_EXTRA_ARGS` | `--limits.retention-period=90d` |
169-
| Tempo | `TEMPO_EXTRA_ARGS` | |
170-
| Pyroscope | `PYROSCOPE_EXTRA_ARGS` | |
171-
| OpenTelemetry Collector | `OTELCOL_EXTRA_ARGS` | |
165+
| Backend | Env var | Example |
166+
|-------------------------|-------------------------|-------------------------------------------|
167+
| Prometheus | `PROMETHEUS_EXTRA_ARGS` | `--storage.tsdb.retention.time=90d` |
168+
| Loki | `LOKI_EXTRA_ARGS` | `--limits.retention-period=90d` |
169+
| Tempo | `TEMPO_EXTRA_ARGS` | `--query-frontend.mcp-server.enabled=true`|
170+
| Pyroscope | `PYROSCOPE_EXTRA_ARGS` | |
171+
| OpenTelemetry Collector | `OTELCOL_EXTRA_ARGS` | |
172172

173173
For example, to set a 90-day retention period for Prometheus:
174174

@@ -385,8 +385,19 @@ cosign verify ${IMAGE} --certificate-identity ${IDENTITY} --certificate-oidc-iss
385385
## AI Tool Integration (MCP)
386386

387387
The stack provides an [MCP][mcp] integration so AI coding tools can query logs, metrics, traces,
388-
and dashboards. Tempo exposes an HTTP MCP endpoint from the container, while Grafana
389-
dashboards and queries are accessed via a client-side MCP server (`uvx mcp-grafana`).
388+
and dashboards. Traces can be queried through Tempo's HTTP MCP endpoint or through the
389+
client-side [Grafana MCP server](https://grafana.com/docs/grafana/latest/developer-resources/mcp/)
390+
(`uvx mcp-grafana`), which also provides access to dashboards, logs, and metrics.
391+
392+
Enable the Tempo MCP server by setting an environment variable:
393+
394+
```sh
395+
TEMPO_EXTRA_ARGS="--query-frontend.mcp-server.enabled=true"
396+
```
397+
398+
```sh
399+
docker run -e TEMPO_EXTRA_ARGS="--query-frontend.mcp-server.enabled=true" grafana/otel-lgtm
400+
```
390401

391402
```sh
392403
docker exec lgtm cat /etc/lgtm/mcp.json # or: podman exec ...

docker/run-all.bats

Lines changed: 143 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env bats
2+
# shellcheck disable=SC2030,SC2031
23
bats_require_minimum_version 1.5.0
34

45
setup() {
@@ -15,16 +16,17 @@ setup() {
1516
run-prometheus.sh \
1617
run-tempo.sh \
1718
run-pyroscope.sh; do
18-
cat >"$TESTDIR/$script" <<'EOF'
19+
cat >"$TESTDIR/$script" <<'SCRIPT'
1920
#!/usr/bin/env bash
2021
sleep 60
21-
EOF
22+
SCRIPT
2223
chmod +x "$TESTDIR/$script"
2324
done
2425

25-
cat >"$TESTDIR/curl" <<'EOF'
26+
cat >"$TESTDIR/curl" <<'SCRIPT'
2627
#!/usr/bin/env bash
2728
args="$*"
29+
mode="${STUB_SA_MODE:-success}"
2830
2931
if [[ "$args" == *"/ready"* ||
3032
"$args" == *"/api/health"* ||
@@ -33,23 +35,45 @@ if [[ "$args" == *"/ready"* ||
3335
exit 0
3436
fi
3537
38+
if [[ "$args" == *"/api/serviceaccounts/1/tokens"* && "$args" == *"-X DELETE"* ]]; then
39+
printf '{}'
40+
exit 0
41+
fi
42+
3643
if [[ "$args" == *"/api/serviceaccounts/1/tokens"* && "$args" == *"-d"* ]]; then
37-
printf '{"key":"token123"}'
44+
if [[ "$mode" == "success" || "$mode" == "with_existing_token" ]]; then
45+
printf '{"key":"token123"}'
46+
fi
3847
exit 0
3948
fi
4049
4150
if [[ "$args" == *"/api/serviceaccounts/1/tokens"* ]]; then
42-
printf '[]'
51+
if [[ "$mode" == "with_existing_token" ]]; then
52+
printf '[{"id":99,"name":"ai-tools-token"}]'
53+
else
54+
printf '[]'
55+
fi
56+
exit 0
57+
fi
58+
59+
if [[ "$args" == *"/api/serviceaccounts/search?query=ai-tools"* ]]; then
60+
if [[ "$mode" == "lookup_existing" ]]; then
61+
printf '{"serviceAccounts":[{"id":1,"name":"ai-tools"}]}'
62+
else
63+
printf '{}'
64+
fi
4365
exit 0
4466
fi
4567
4668
if [[ "$args" == *"/api/serviceaccounts"* && "$args" == *"-d"* ]]; then
47-
printf '{"id":1}'
69+
if [[ "$mode" == "success" || "$mode" == "with_existing_token" ]]; then
70+
printf '{"id":1}'
71+
fi
4872
exit 0
4973
fi
5074
5175
printf '{}'
52-
EOF
76+
SCRIPT
5377
chmod +x "$TESTDIR/curl"
5478
}
5579

@@ -58,7 +82,7 @@ teardown() {
5882
}
5983

6084
run_run_all() {
61-
local version=$1
85+
local version=${1:-latest}
6286
cd "$TESTDIR" || return 1
6387
PATH="$TESTDIR:$PATH" \
6488
LGTM_CONFIG_DIR="$CONFIGDIR" \
@@ -68,78 +92,141 @@ run_run_all() {
6892
timeout 3s bash ./run-all.sh
6993
}
7094

95+
run_mcp_case() {
96+
local tempo_enabled=$1
97+
local sa_mode=$2
98+
local version=${3:-latest}
99+
if [[ "$tempo_enabled" == "true" ]]; then
100+
export TEMPO_EXTRA_ARGS="--query-frontend.mcp-server.enabled=true"
101+
else
102+
unset TEMPO_EXTRA_ARGS
103+
fi
104+
export STUB_SA_MODE="$sa_mode"
105+
run run_run_all "$version"
106+
}
107+
108+
assert_contains() {
109+
local needle=$1
110+
[[ "$output" == *"$needle"* ]]
111+
}
112+
113+
assert_not_contains() {
114+
local needle=$1
115+
[[ "$output" != *"$needle"* ]]
116+
}
117+
118+
assert_has_file() {
119+
[ -f "$1" ]
120+
}
121+
122+
assert_no_file() {
123+
[ ! -f "$1" ]
124+
}
125+
126+
assert_file_contains() {
127+
grep -Fq "$2" "$1"
128+
}
129+
130+
assert_file_not_contains() {
131+
! grep -Fq "$2" "$1"
132+
}
133+
71134
@test "docs URL uses main for latest" {
72135
local expected="https://github.com/grafana/docker-otel-lgtm/blob/main/docs/mcp-integration.md"
73-
local expected_line=" Docs: $expected"
74-
run run_run_all "latest"
75-
[[ "$output" == *"$expected_line"* ]]
76-
[[ "$output" != *"/blob/vlatest/"* ]]
136+
run run_run_all latest
137+
assert_contains " Docs: $expected"
138+
assert_not_contains "/blob/vlatest/"
77139
}
78140

79141
@test "docs URL uses main when version is empty" {
80142
local expected="https://github.com/grafana/docker-otel-lgtm/blob/main/docs/mcp-integration.md"
81-
local expected_line=" Docs: $expected"
82143
run run_run_all ""
83-
[[ "$output" == *"$expected_line"* ]]
144+
assert_contains " Docs: $expected"
84145
}
85146

86-
87147
@test "docs URL uses main for main tag" {
88148
local expected="https://github.com/grafana/docker-otel-lgtm/blob/main/docs/mcp-integration.md"
89-
local expected_line=" Docs: $expected"
90-
run run_run_all "main"
91-
[[ "$output" == *"$expected_line"* ]]
92-
[[ "$output" != *"/blob/vmain/"* ]]
149+
run run_run_all main
150+
assert_contains " Docs: $expected"
151+
assert_not_contains "/blob/vmain/"
93152
}
94153

95154
@test "printed MCP commands escape configurable paths" {
96155
local configdir="$TESTDIR/etc/lgtm with spaces"
97156
local escaped_configdir=${configdir// /\\ }
98-
CONFIGDIR="$configdir" run run_run_all "latest"
99-
[[ "$output" == *"bash <(docker exec lgtm cat ${escaped_configdir}/claude-mcp-setup.sh)"* ]]
100-
[[ "$output" == *"docker exec lgtm cat ${escaped_configdir}/mcp.json"* ]]
157+
CONFIGDIR="$configdir" run run_run_all latest
158+
assert_contains "bash <(docker exec lgtm cat ${escaped_configdir}/claude-mcp-setup.sh)"
159+
assert_contains "docker exec lgtm cat ${escaped_configdir}/mcp.json"
101160
}
102161

103162
@test "docs URL prefixes bare release version with v" {
104163
local expected
105164
expected="https://github.com/grafana/docker-otel-lgtm/blob/v1.2.3-test/docs/mcp-integration.md"
106-
local expected_line=" Docs: $expected"
107-
run run_run_all "1.2.3-test"
108-
[[ "$output" == *"$expected_line"* ]]
165+
run run_run_all 1.2.3-test
166+
assert_contains " Docs: $expected"
109167
}
110168

111169
@test "docs URL does not double-prefix version that already starts with v" {
112170
local expected
113171
expected="https://github.com/grafana/docker-otel-lgtm/blob/v1.2.3-test/docs/mcp-integration.md"
114-
local expected_line=" Docs: $expected"
115-
run run_run_all "v1.2.3-test"
116-
[[ "$output" == *"$expected_line"* ]]
117-
[[ "$output" != *"/blob/vv1.2.3-test/"* ]]
118-
}
119-
120-
@test "MCP bootstrap writes helper artifacts with expected contents" {
121-
run run_run_all "latest"
122-
[ -f "$CONFIGDIR/mcp.json" ]
123-
[ -f "$CONFIGDIR/claude-mcp-setup.sh" ]
124-
[ -f "$TOKENFILE" ]
125-
126-
grep -Fq \
127-
'"GRAFANA_URL": "http://localhost:3000"' \
128-
"$CONFIGDIR/mcp.json"
129-
grep -Fq \
130-
'"GRAFANA_SERVICE_ACCOUNT_TOKEN": "token123"' \
131-
"$CONFIGDIR/mcp.json"
132-
grep -Fq '"url": "http://localhost:3200/api/mcp"' "$CONFIGDIR/mcp.json"
133-
134-
grep -Fq \
135-
'claude mcp add grafana -e "GRAFANA_URL=http://localhost:3000"' \
136-
"$CONFIGDIR/claude-mcp-setup.sh"
137-
grep -Fq \
138-
'GRAFANA_SERVICE_ACCOUNT_TOKEN=token123' \
139-
"$CONFIGDIR/claude-mcp-setup.sh"
140-
grep -Fq \
141-
'claude mcp add --transport http tempo "http://localhost:3200/api/mcp"' \
142-
"$CONFIGDIR/claude-mcp-setup.sh"
143-
144-
grep -Fqx 'token123' "$TOKENFILE"
172+
run run_run_all v1.2.3-test
173+
assert_contains " Docs: $expected"
174+
assert_not_contains "/blob/vv1.2.3-test/"
175+
}
176+
177+
@test "tempo enabled with service account writes both MCP servers" {
178+
run_mcp_case true success latest
179+
assert_contains "Tempo MCP: server enabled at http://localhost:3200/api/mcp"
180+
assert_contains "Grafana MCP: server enabled with service account token"
181+
assert_contains " - 3200: Tempo endpoint (MCP at http://localhost:3200/api/mcp)"
182+
assert_has_file "$CONFIGDIR/mcp.json"
183+
assert_has_file "$CONFIGDIR/claude-mcp-setup.sh"
184+
assert_has_file "$TOKENFILE"
185+
assert_file_contains "$CONFIGDIR/mcp.json" '"grafana"'
186+
assert_file_contains "$CONFIGDIR/mcp.json" '"tempo"'
187+
assert_file_contains "$CONFIGDIR/mcp.json" 'GRAFANA_SERVICE_ACCOUNT_TOKEN": "token123"'
188+
assert_file_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add grafana'
189+
assert_file_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add --transport http tempo'
190+
assert_file_contains "$TOKENFILE" 'token123'
191+
}
192+
193+
@test "tempo disabled with service account writes grafana-only MCP config" {
194+
run_mcp_case false success latest
195+
assert_contains "Tempo MCP: server disabled; enable with"
196+
assert_contains "TEMPO_EXTRA_ARGS=--query-frontend.mcp-server.enabled=true"
197+
assert_contains "Grafana MCP: server enabled with service account token"
198+
assert_contains " - 3200: Tempo endpoint"
199+
assert_not_contains " - 3200: Tempo endpoint (MCP at http://localhost:3200/api/mcp)"
200+
assert_has_file "$CONFIGDIR/mcp.json"
201+
assert_has_file "$CONFIGDIR/claude-mcp-setup.sh"
202+
assert_has_file "$TOKENFILE"
203+
assert_file_contains "$CONFIGDIR/mcp.json" '"grafana"'
204+
assert_file_not_contains "$CONFIGDIR/mcp.json" '"tempo"'
205+
assert_file_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add grafana'
206+
assert_file_not_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add --transport http tempo'
207+
}
208+
209+
@test "tempo enabled without service account writes tempo-only MCP config" {
210+
run_mcp_case true missing latest
211+
assert_contains "Tempo MCP: server enabled at http://localhost:3200/api/mcp"
212+
assert_contains "Grafana MCP: server unavailable; could not create service account token"
213+
assert_has_file "$CONFIGDIR/mcp.json"
214+
assert_has_file "$CONFIGDIR/claude-mcp-setup.sh"
215+
assert_no_file "$TOKENFILE"
216+
assert_file_not_contains "$CONFIGDIR/mcp.json" '"grafana"'
217+
assert_file_contains "$CONFIGDIR/mcp.json" '"tempo"'
218+
assert_file_not_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add grafana'
219+
assert_file_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add --transport http tempo'
220+
}
221+
222+
@test "tempo disabled without service account writes empty MCP config" {
223+
run_mcp_case false missing latest
224+
assert_contains "Tempo MCP: server disabled; enable with"
225+
assert_contains "TEMPO_EXTRA_ARGS=--query-frontend.mcp-server.enabled=true"
226+
assert_contains "Grafana MCP: server unavailable; could not create service account token"
227+
assert_has_file "$CONFIGDIR/mcp.json"
228+
assert_has_file "$CONFIGDIR/claude-mcp-setup.sh"
229+
assert_no_file "$TOKENFILE"
230+
assert_file_contains "$CONFIGDIR/mcp.json" '"mcpServers": {}'
231+
assert_file_not_contains "$CONFIGDIR/claude-mcp-setup.sh" 'claude mcp add '
145232
}

0 commit comments

Comments
 (0)