This harness proves that the instrumentation patterns the skills teach actually work against a real Netdata instance.
- Starts Netdata in Docker with OTLP/gRPC enabled on port 4317.
- Builds and runs an instrumented Node.js or Python sample app on the host.
- Generates traffic against the sample app for 30 seconds.
- Queries Netdata (via MCP, falling back to REST) to confirm the sample app's metrics arrived.
Exit code 0 means the full pipeline works. Non-zero means something in the skill-taught pattern is broken and a user following the skill would not see their metrics.
This repo's build machine has ports 19999 and 8080 in use. The harness uses the following host ports, remapped from the usual defaults:
- Netdata dashboard and MCP:
http://localhost:19998(container internal still uses 19999). - OTLP gRPC:
localhost:4317(no remap; 4317 is free). - Sample app:
http://localhost:8088(the app listens on whateverPORTenv var it receives).
Skill content continues to document the standard defaults (19999 and 8080) because those are what users see on clean machines. The overrides are only used inside this harness.
dockeranddocker composenode>= 20 andnpmpython>= 3.10 andpipnc,curl,jq- ports 19998, 4317, 8088 free on the host
bash tests/e2e/run-e2e.sh nodejs
# or
bash tests/e2e/run-e2e.sh pythonAdd a second argument to also verify the metric arrived in Netdata Cloud:
bash tests/e2e/run-e2e.sh nodejs cloudCloud mode requires three environment variables:
NETDATA_CLAIM_TOKENclaim token from the target Cloud spaceNETDATA_CLAIM_ROOMScomma-separated room IDsNETDATA_CLOUD_API_TOKENbearer token for the Cloud MCP probe
Optional: NETDATA_CLAIM_URL (default https://app.netdata.cloud)
and NETDATA_CLOUD_MCP_URL (default
https://app.netdata.cloud/api/v1/mcp).
The Cloud probe pauses 45 seconds after local verification to let
the Agent stream + Cloud aggregate, then queries the Cloud MCP with
the same list_metrics q-filter pattern used locally.
The script is trap-safe: it stops the sample app and tears down the Docker container on exit, even on failure.
verify-metrics.py prefers MCP:
docker execinto the Netdata container to read the bearer token from/var/lib/netdata/mcp_dev_preview_api_key.- Call
initialize→tools/list→tools/call list_metricsvia HTTP-streamable JSON-RPC athttp://localhost:19998/mcp. - Invoke
list_metricswith{"metrics": "*", "nodes": "*", "q": <service-name>}. Netdata enters SEARCH mode whenqis set and includes labels, instances, and dimensions in the response (see_reference/netdata/src/web/mcp/mcp-tools-list-metadata.c:503-506). The OTelservice.nameattribute rides through as an instance label. - Parse the JSON-encoded
result.content[0].textpayload and count matched contexts. Any non-zero count confirms metadata matching the service name exists on the Netdata side.
If the MCP path fails (connection error, unparsable payload, zero
matches), the script falls back to Netdata's REST /api/v2/contexts.
REST is the safety net so transient MCP quirks do not gate the
release, but the MCP path is what the skill teaches and what CI
should report on.
tests/e2e/
├── README.md # you are here
├── docker-compose.yml # Netdata container definition
├── netdata-config/otel.yaml # minimal OTLP receiver config
├── sample-apps/
│ ├── nodejs/
│ │ ├── package.json
│ │ ├── instrument.js # must match skills/netdata-instrumentation/rules/nodejs.md
│ │ └── index.js # minimal Express /hello handler
│ └── python/
│ ├── requirements.txt
│ ├── instrument.py # must match skills/netdata-instrumentation/rules/python.md
│ └── app.py # minimal Flask /hello handler
├── traffic.sh # curl loop
├── verify-metrics.py # local Agent MCP + REST probe
├── verify-metrics-cloud.py # Netdata Cloud MCP probe (cloud mode)
├── run-e2e.sh # orchestrator
└── .gitignore # node_modules, .venv, etc.
The SDK init code in sample-apps/nodejs/instrument.js and
sample-apps/python/instrument.py must match the code block in the
matching rule file (skills/netdata-instrumentation/rules/nodejs.md,
rules/python.md) byte for byte. The fixture is the source of truth:
if the fixture runs green, the skill teaches a working pattern. A PR
that edits one must edit the other in the same commit.
- Add
sample-apps/<lang>/with the same shape (SDK init + simple web handler). - Add a branch in
run-e2e.shthat installs deps and starts the service. - Run the harness. If it fails, fix the SDK init in both the fixture and the matching rule file.
Typical failure modes:
- Netdata web never comes up:
docker compose logs netdatahas the answer. Usually a port collision or an unreadable volume mount. - OTLP port never opens: the plugin failed to load. Look for
otel-pluginlines indocker compose logs netdata. - Sample app connects but no metrics appear: usually the app's
OTEL_EXPORTER_OTLP_ENDPOINTis wrong, or it is using the HTTP exporter by accident. Verify the fixture uses@opentelemetry/exporter-metrics-otlp-grpc(Node.js) oropentelemetry.exporter.otlp.proto.grpc(Python). - MCP handshake fails: check the bearer token via
docker exec netdata-skills-e2e cat /var/lib/netdata/mcp_dev_preview_api_key.