Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
90 changes: 90 additions & 0 deletions .github/workflows/mcp-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: MCP e2e

# Runs the BTrace MCP server end-to-end tests against a BTrace distribution built from source, so the
# job never depends on a published or locally installed io.btrace:btrace artifact. No LLM is involved.

on:
push:
branches: [main]
paths:
- 'plugins/btrace-observability/mcp/**'
- '.github/workflows/mcp-e2e.yml'
pull_request:
paths:
- 'plugins/btrace-observability/mcp/**'
- '.github/workflows/mcp-e2e.yml'
workflow_dispatch:
inputs:
btrace-ref:
description: 'BTrace branch, tag or SHA to build (default: the repository default branch)'
required: false
default: ''

permissions:
contents: read

concurrency:
group: mcp-e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# setup-java registers every JDK in ~/.m2/toolchains.xml, where Gradle discovers toolchains.
# BTrace builds with toolchains 8 to 26; the MCP module uses 11. The last entry is JAVA_HOME.
- name: Set up JDKs
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
with:
distribution: temurin
java-version: |
8
11
17
24
26
21

- name: Check out agent plugins
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Check out BTrace
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
repository: btraceio/btrace
ref: ${{ inputs.btrace-ref }}
path: btrace

- name: Set up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6

- name: Build the BTrace distribution JAR
working-directory: btrace
run: ./gradlew --no-daemon :btrace-dist:btraceJar

- name: Locate the BTrace JAR
run: |
shopt -s nullglob
jars=("$GITHUB_WORKSPACE"/btrace/btrace-dist/build/resources/main/v*/libs/btrace.jar)
if [ "${#jars[@]}" -ne 1 ]; then
echo "::error::expected exactly one btrace.jar, found ${#jars[@]}: ${jars[*]}"
exit 1
fi
echo "BTRACE_JAR=${jars[0]}" >> "$GITHUB_ENV"
echo "Using ${jars[0]} from BTrace $(git -C btrace rev-parse --short HEAD)"

- name: Run MCP end-to-end tests
working-directory: plugins/btrace-observability/mcp
run: ./gradlew --no-daemon e2eTest

- name: Upload e2e logs and reports
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: mcp-e2e-logs
retention-days: 7
path: |
plugins/btrace-observability/mcp/build/e2e
plugins/btrace-observability/mcp/build/reports/tests/e2eTest
plugins/btrace-observability/mcp/build/test-results/e2eTest
9 changes: 9 additions & 0 deletions plugins/btrace-observability/.codex-plugin/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"mcpServers": {
"btrace": {
"command": "jbang",
"args": ["mcp/src/main/java/io/btrace/mcp/BTraceMcpServer.java"],
"cwd": "."
}
}
}
1 change: 1 addition & 0 deletions plugins/btrace-observability/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"diagnostics"
],
"skills": "./skills/",
"mcpServers": "./.codex-plugin/mcp.json",
"interface": {
"displayName": "BTrace Observability",
"shortDescription": "Diagnose Java behavior with focused probes.",
Expand Down
18 changes: 18 additions & 0 deletions plugins/btrace-observability/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,21 @@ Run its unit tests with:
```

`./gradlew check` enforces at least 80% line coverage for the MCP Java code.

Run the end-to-end tests with:

```sh
./gradlew e2eTest
```

They exercise every tool and prompt against a sample JVM, observing probe effects through `@Export`
counters read with `jcmd`. No LLM is involved. By default they launch the server through JBang, as
the plugin manifests do, which needs `jbang` on `PATH` and a resolvable `io.btrace:btrace` artifact,
so they are not part of `check`. To test against a BTrace build instead, such as in CI, point them
at its masked JAR:

```sh
BTRACE_JAR=/path/to/btrace/btrace-dist/build/resources/main/v<version>/libs/btrace.jar ./gradlew e2eTest
```

The `list_probes` case is disabled because the BTrace client exits the JVM after listing probes.
37 changes: 37 additions & 0 deletions plugins/btrace-observability/mcp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,43 @@ tasks.withType(Test).configureEach {
jvmArgs '--add-modules', 'jdk.attach'
}

// End-to-end tests run the real server against a live sample JVM, so they are not part of `check`.
// By default the server is launched through JBang, as the plugin manifests do, which needs jbang on
// PATH and a resolvable io.btrace:btrace artifact. Set BTRACE_JAR (or -PbtraceJar) to a masked
// btrace.jar, e.g. from a CI build of BTrace, to run the compiled server against that JAR instead.
def btraceJar = providers.gradleProperty('btraceJar')
.orElse(providers.environmentVariable('BTRACE_JAR'))
.map { it.trim() ? it.trim() : null }

sourceSets {
e2eTest
}

configurations {
e2eTestImplementation.extendsFrom testImplementation
e2eTestRuntimeOnly.extendsFrom testRuntimeOnly
}

dependencies {
e2eTestImplementation sourceSets.main.output
}

tasks.register('e2eTest', Test) {
description = 'Runs the MCP server end-to-end against a sample JVM.'
group = 'verification'
testClassesDirs = sourceSets.e2eTest.output.classesDirs
classpath = sourceSets.e2eTest.runtimeClasspath
shouldRunAfter tasks.named('test')
outputs.upToDateWhen { false }
systemProperty 'e2e.serverScript', file('src/main/java/io/btrace/mcp/BTraceMcpServer.java').absolutePath
systemProperty 'e2e.sampleClasspath', sourceSets.e2eTest.output.classesDirs.asPath
systemProperty 'e2e.logDir', layout.buildDirectory.dir('e2e').get().asFile.absolutePath
if (btraceJar.isPresent()) {
systemProperty 'e2e.btraceJar', file(btraceJar.get()).absolutePath
systemProperty 'e2e.serverClasspath', sourceSets.main.runtimeClasspath.asPath
}
}

jacoco {
toolVersion = '0.8.13'
}
Expand Down
Loading
Loading