From 00b3df59c6cfdbd6cbce247b5e58913c6556bed0 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 30 Oct 2026 10:43:22 -0400 Subject: [PATCH 1/2] feat(claude): add hosted braintrust mcp plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > ๐Ÿค– **This text was AI-generated.** The Claude marketplace offered session tracing but did not provide a packaged connection to Braintrust's hosted MCP tools. Add a separate Braintrust plugin that connects through OAuth, alongside the existing tracing plugin: ```text Claude marketplace โ”œโ”€โ”€ braintrust โ†’ hosted MCP server โ””โ”€โ”€ trace-claude-code โ†’ bt tracing ``` Document installation and data handling, validate the MCP-only package, and keep the marketplace and plugin versions aligned during releases. Anthropic directory submission remains a manual step. --- .github/workflows/release.yml | 4 +- AGENTS.md | 6 +- README.md | 11 ++-- scripts/set-plugin-version.py | 8 ++- src/plugins/claude/build.sh | 1 + .../content/.claude-plugin/marketplace.json | 10 ++- src/plugins/claude/content/AGENTS.md | 10 ++- src/plugins/claude/content/README.md | 23 +++++-- .../braintrust/.claude-plugin/icon.png | Bin 0 -> 31194 bytes .../braintrust/.claude-plugin/plugin.json | 21 ++++++ .../content/plugins/braintrust/.mcp.json | 8 +++ .../claude/content/plugins/braintrust/LICENSE | 21 ++++++ .../content/plugins/braintrust/README.md | 59 +++++++++++++++++ src/plugins/claude/validate.sh | 60 ++++++++++++++++-- 14 files changed, 219 insertions(+), 23 deletions(-) create mode 100644 src/plugins/claude/content/plugins/braintrust/.claude-plugin/icon.png create mode 100644 src/plugins/claude/content/plugins/braintrust/.claude-plugin/plugin.json create mode 100644 src/plugins/claude/content/plugins/braintrust/.mcp.json create mode 100644 src/plugins/claude/content/plugins/braintrust/LICENSE create mode 100644 src/plugins/claude/content/plugins/braintrust/README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8dae338c..fc11d2fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: printf 'dist_repo=%s\n' "$dist_repo" printf 'paths<.zip` to the distribution GitHub Release. OpenAI submission remains manual. + Claude releases update `plugins/braintrust` in the distribution repository. Anthropic directory submission remains manual, from the developer portal. + Human approval and merge into `main` authorize tagging the merge commit and publishing this version to the production destination. This preparation does not tag or deploy anything. - name: Summarize release pull request diff --git a/AGENTS.md b/AGENTS.md index 28ea4158..cf584a2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,9 +67,9 @@ Versioning is per distribution. Claude, Codex, Cursor, and Grok plugins carry their version in their plugin manifests. `scripts/set-plugin-version.py` updates those manifests for a release. Antigravity's schema does not expose a version field, so its distribution is versioned by repository tags and GitHub Releases. -Claude's marketplace manifest carries a separate catalog version; bump it -independently for breaking marketplace changes. The Codex marketplace manifest -is unversioned. +Claude's marketplace manifest carries a catalog version that the same script +keeps in sync with its plugins; Claude Code does not read it. The Codex +marketplace manifest is unversioned. | Agent | Distribution repository | |---|---| diff --git a/README.md b/README.md index 94bbe5ba..626fcba2 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ This monorepo contains the plugins and the shared Rust tracing daemon embedded in the Braintrust CLI (`bt`). Plugins forward native events locally; the daemon builds traces, journals events for recovery, and delivers spans to Braintrust. -This repository also packages the [Braintrust -plugin](src/plugins/codex/content/plugins/braintrust/README.md) for submission to -the official ChatGPT and Codex plugin directory. It connects to Braintrust's -hosted MCP server through OAuth and requires no `bt` installation. Installing it -does not enable session tracing. +This repository also packages the Braintrust MCP plugin for the official +directories: the [Codex package](src/plugins/codex/content/plugins/braintrust/README.md) +for ChatGPT and Codex, and the [Claude +package](src/plugins/claude/content/plugins/braintrust/README.md) for Anthropic's +directory. Each connects to Braintrust's hosted MCP server through OAuth and +requires no `bt` installation. Installing it does not enable session tracing. ## Get started diff --git a/scripts/set-plugin-version.py b/scripts/set-plugin-version.py index 22ffdfdf..185d14ce 100644 --- a/scripts/set-plugin-version.py +++ b/scripts/set-plugin-version.py @@ -2,7 +2,8 @@ """Set a release version on every distributed version surface for an agent. Versioning is per distribution: portable root manifests and compatibility -manifests share one version. Marketplace manifests are NOT touched. +manifests share one version. Claude's marketplace catalog version follows its +plugins; other marketplace manifests are NOT touched. Only version values are rewritten, so surrounding files keep their formatting. @@ -43,6 +44,11 @@ def main() -> None: manifests.extend(matches) surfaces = [(path, VERSION_RE, "version") for path in manifests] + if agent == "claude": + # Top-level catalog version; plugin entries carry no version of their own. + surfaces.append( + ("src/plugins/claude/content/.claude-plugin/marketplace.json", VERSION_RE, "catalog version") + ) if agent == "cursor": content = "src/plugins/cursor/content" surfaces.extend([ diff --git a/src/plugins/claude/build.sh b/src/plugins/claude/build.sh index a7676728..8bc99429 100755 --- a/src/plugins/claude/build.sh +++ b/src/plugins/claude/build.sh @@ -4,6 +4,7 @@ # # The Claude Code marketplace consumes a repo whose ROOT is the marketplace: # .claude-plugin/marketplace.json marketplace manifest +# plugins/braintrust/ hosted MCP plugin (Anthropic directory listing) # plugins/trace-claude-code/ tracing plugin (thin daemon hook shim) # # Everything is plain shell + config โ€” no compiled artifacts โ€” so the whole diff --git a/src/plugins/claude/content/.claude-plugin/marketplace.json b/src/plugins/claude/content/.claude-plugin/marketplace.json index d6f66bd9..0729b7e6 100644 --- a/src/plugins/claude/content/.claude-plugin/marketplace.json +++ b/src/plugins/claude/content/.claude-plugin/marketplace.json @@ -1,13 +1,19 @@ { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "braintrust-claude-plugin", - "version": "2.0.0", - "description": "Braintrust session tracing for Claude Code", + "version": "3.0.5", + "description": "Braintrust MCP tools and session tracing for Claude Code", "owner": { "name": "Braintrust", "email": "support@braintrust.dev" }, "plugins": [ + { + "name": "braintrust", + "description": "Explore Braintrust traces, manage prompts and datasets, and run evaluations through the hosted Braintrust MCP server.", + "source": "./plugins/braintrust", + "category": "development" + }, { "name": "trace-claude-code", "description": "Automatically trace Claude Code conversations to Braintrust. Captures user messages, assistant responses, and tool calls for observability.", diff --git a/src/plugins/claude/content/AGENTS.md b/src/plugins/claude/content/AGENTS.md index b631f338..3ccc0c6d 100644 --- a/src/plugins/claude/content/AGENTS.md +++ b/src/plugins/claude/content/AGENTS.md @@ -2,7 +2,7 @@ ## About this repository -This is the **Braintrust Claude Code plugin marketplace** - a repository that distributes Claude Code tracing for Braintrust. +This is the **Braintrust Claude Code plugin marketplace** - a generated repository that distributes the Braintrust MCP plugin and Claude Code tracing for Braintrust. ### Structure @@ -11,6 +11,7 @@ claude-plugin/ โ”œโ”€โ”€ .claude-plugin/ โ”‚ โ””โ”€โ”€ marketplace.json # Marketplace catalog (lists available plugins) โ”œโ”€โ”€ plugins/ +โ”‚ โ”œโ”€โ”€ braintrust/ # Plugin: Hosted Braintrust MCP server (directory listing) โ”‚ โ””โ”€โ”€ trace-claude-code/ # Plugin: Session tracing to Braintrust โ””โ”€โ”€ evals/ # Evaluation suite for Braintrust MCP behavior ``` @@ -19,8 +20,15 @@ claude-plugin/ | Plugin | Description | |--------|-------------| +| `braintrust` | Connects to the hosted Braintrust MCP server over HTTP with OAuth. No hooks, skills, commands, or local processes. Submitted to Anthropic's directory. | | `trace-claude-code` | Forwards Claude Code lifecycle hooks to `bt trace hook --source claude-code`; the shared daemon builds and delivers traces. | +Both plugins share one release version. Make source changes in +`braintrustdata/braintrust-coding-agent-plugins` and release through its Release +Plugin workflow; do not edit or tag this generated repository manually. Never +rename the `braintrust` plugin: directory and marketplace installs are keyed +by name. + ### Terminology - **Marketplace**: A repository with a `marketplace.json` that catalogs multiple plugins for distribution diff --git a/src/plugins/claude/content/README.md b/src/plugins/claude/content/README.md index 1f088c31..982c0589 100644 --- a/src/plugins/claude/content/README.md +++ b/src/plugins/claude/content/README.md @@ -1,11 +1,26 @@ -# Braintrust tracing for Claude Code +# Braintrust plugins for Claude Code > **This repository is generated.** It is built from > [braintrustdata/braintrust-coding-agent-plugins](https://github.com/braintrustdata/braintrust-coding-agent-plugins). > Don't edit files here โ€” make changes and file issues in that repository, and they > will be rebuilt into this one. -A Claude Code plugin marketplace for tracing Claude Code sessions to [Braintrust](https://braintrust.dev). +This marketplace contains two independently opt-in plugins: + +- **Braintrust**, `braintrust@braintrust-claude-plugin`: hosted MCP tools with + OAuth. No `bt` dependency or tracing hooks. See the + [MCP installation and data plane guide](plugins/braintrust/README.md). +- **Trace Claude Code**, `trace-claude-code@braintrust-claude-plugin`: session + tracing to [Braintrust](https://braintrust.dev) via `bt`. + +For MCP, install Braintrust from this marketplace: + +```bash +claude plugin marketplace add braintrustdata/braintrust-claude-plugin +claude plugin install braintrust@braintrust-claude-plugin +``` + +The sections below describe optional tracing setup only. ## Prerequisites @@ -43,8 +58,8 @@ Every registered lifecycle event is forwarded synchronously to `bt trace hook --source claude-code`, preserving per-session ordering. Hook failures never fail a Claude Code turn. -This marketplace does not install or configure the Braintrust MCP server. Use -your agent's native connector or MCP configuration when you want MCP access. +Tracing setup does not install the Braintrust MCP plugin. MCP sign-in and `bt` +tracing sign-in are independent. ## Data handling diff --git a/src/plugins/claude/content/plugins/braintrust/.claude-plugin/icon.png b/src/plugins/claude/content/plugins/braintrust/.claude-plugin/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cca9e0267b27b0302d75aec9cb892a42bf9e91 GIT binary patch literal 31194 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 Data plane**. | + +## Support + +See [Braintrust troubleshooting](https://www.braintrust.dev/docs/kb/troubleshooting-guides) +or contact [support@braintrust.dev](mailto:support@braintrust.dev). +[Privacy policy](https://www.braintrust.dev/legal/privacy-policy) ยท +[Terms of service](https://www.braintrust.dev/legal/terms-of-service). diff --git a/src/plugins/claude/validate.sh b/src/plugins/claude/validate.sh index e322c487..2b95a070 100755 --- a/src/plugins/claude/validate.sh +++ b/src/plugins/claude/validate.sh @@ -3,7 +3,8 @@ # validate.sh โ€” Sanity-check a built Claude Code dist tree in $1 before publishing. # # Fails (non-zero) on the mistakes that would ship a broken marketplace: -# - missing marketplace manifest / tracing plugin manifest / hooks +# - missing marketplace manifest / plugin manifests / hooks +# - an MCP plugin that ships anything beyond its hosted server connection # - malformed JSON in any manifest # - marketplace entries whose `source` path does not exist in the tree # @@ -30,6 +31,8 @@ check_json "$MARKETPLACE" # Required files for each shipped plugin. required=( + "plugins/braintrust/.claude-plugin/plugin.json" + "plugins/braintrust/.mcp.json" "plugins/trace-claude-code/.claude-plugin/plugin.json" "plugins/trace-claude-code/.cursor-plugin/plugin.json" "plugins/trace-claude-code/hooks/hooks.json" @@ -40,7 +43,7 @@ for rel in "${required[@]}"; do done python3 - "$MARKETPLACE" "$TARGET_DIR/plugins" <<'PY' \ - || fail "Claude marketplace must contain only the tracing plugin" + || fail "Claude marketplace must contain exactly the MCP and tracing plugins" import json import sys from pathlib import Path @@ -48,14 +51,59 @@ from pathlib import Path with open(sys.argv[1]) as f: plugins = json.load(f)["plugins"] -assert [plugin["name"] for plugin in plugins] == ["trace-claude-code"] +assert [plugin["name"] for plugin in plugins] == ["braintrust", "trace-claude-code"] +for plugin in plugins: + assert plugin["source"] == f"./plugins/{plugin['name']}" assert sorted(path.name for path in Path(sys.argv[2]).iterdir() if path.is_dir()) == [ - "trace-claude-code" + "braintrust", "trace-claude-code" ] PY -if find "$TARGET_DIR" -name '.mcp.json' -print -quit | grep -q .; then - fail "Claude dist still contains an MCP proxy configuration" +# The braintrust plugin is the Anthropic directory listing: a hosted MCP +# connection only, with no hooks, skills, commands, or local processes. +python3 - "$TARGET_DIR/plugins" <<'PY' || fail "Claude braintrust plugin breaks its MCP-only contract" +import json +import struct +import sys +from pathlib import Path + +plugins = Path(sys.argv[1]) +root = plugins / "braintrust" +assert not any(p.is_symlink() for p in root.rglob("*")) +files = {p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file()} +assert files == { + ".claude-plugin/plugin.json", ".claude-plugin/icon.png", ".mcp.json", "README.md", "LICENSE", +}, files + +manifest = json.loads((root / ".claude-plugin/plugin.json").read_text()) +assert manifest["name"] == "braintrust" and manifest["displayName"] == "Braintrust" +assert manifest["author"]["name"] == "Braintrust" +assert manifest["license"] == "MIT" +assert manifest["icon"] == "./.claude-plugin/icon.png" +for field in ["homepage", "repository", "documentationUrl", "supportUrl", "privacyPolicyUrl", "termsOfServiceUrl"]: + assert manifest[field].startswith("https://"), field +assert not ({"hooks", "commands", "agents", "skills", "lspServers", "mcpServers"} & set(manifest)) + +tracing = json.loads((plugins / "trace-claude-code/.claude-plugin/plugin.json").read_text()) +assert manifest["version"] == tracing["version"], "Claude plugins must share a release version" +marketplace = json.loads((plugins.parent / ".claude-plugin/marketplace.json").read_text()) +assert marketplace["version"] == manifest["version"], "Claude catalog version must match its plugins" +assert not any("version" in entry for entry in marketplace["plugins"]) + +# No headers or credentials: Claude Code discovers OAuth from the server. +assert json.loads((root / ".mcp.json").read_text()) == { + "mcpServers": {"braintrust": {"type": "http", "url": "https://api.braintrust.dev/mcp"}}, +} + +# Anthropic directory listing icon: square PNG, 512..2048px, under 2 MB. +data = (root / ".claude-plugin/icon.png").read_bytes() +assert data[:8] == b"\x89PNG\r\n\x1a\n" and data[12:16] == b"IHDR" and len(data) < 2 * 1024 * 1024 +width, height = struct.unpack(">II", data[16:24]) +assert width == height and 512 <= width <= 2048, (width, height) +PY + +if find "$TARGET_DIR" -name '.mcp.json' ! -path "$TARGET_DIR/plugins/braintrust/.mcp.json" -print -quit | grep -q .; then + fail "Claude dist contains an MCP configuration outside plugins/braintrust" fi python3 - "$TARGET_DIR/plugins/trace-claude-code/hooks/hooks.json" <<'PY' \ From c89c9256609a4c1d1769cce12132b3e102312620 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 30 Oct 2026 14:21:10 -0400 Subject: [PATCH 2/2] fix(claude): use plugin server-map shape for .mcp.json Plugin-root .mcp.json is the server map itself, so drop the mcpServers wrapper that kept braintrust from registering. Also show the supported slash-command install syntax in the plugin README. Co-Authored-By: Claude Opus 5.5 --- src/plugins/claude/content/plugins/braintrust/.mcp.json | 8 +++----- src/plugins/claude/content/plugins/braintrust/README.md | 5 +++-- src/plugins/claude/validate.sh | 3 ++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/claude/content/plugins/braintrust/.mcp.json b/src/plugins/claude/content/plugins/braintrust/.mcp.json index 607acdb2..a6149a03 100644 --- a/src/plugins/claude/content/plugins/braintrust/.mcp.json +++ b/src/plugins/claude/content/plugins/braintrust/.mcp.json @@ -1,8 +1,6 @@ { - "mcpServers": { - "braintrust": { - "type": "http", - "url": "https://api.braintrust.dev/mcp" - } + "braintrust": { + "type": "http", + "url": "https://api.braintrust.dev/mcp" } } diff --git a/src/plugins/claude/content/plugins/braintrust/README.md b/src/plugins/claude/content/plugins/braintrust/README.md index e47cc6a3..1098e05b 100644 --- a/src/plugins/claude/content/plugins/braintrust/README.md +++ b/src/plugins/claude/content/plugins/braintrust/README.md @@ -8,7 +8,8 @@ Tools use that account's permissions and can read or change Braintrust data. ## Install ```text -/plugin install braintrust --marketplace braintrustdata/braintrust-claude-plugin +/plugin marketplace add braintrustdata/braintrust-claude-plugin +/plugin install braintrust@braintrust-claude-plugin ``` Or from your shell: @@ -42,7 +43,7 @@ Braintrust data in response. The packaged server URL is Braintrust's North America (US) data plane. To use another data plane, copy this plugin into your own marketplace and replace -`mcpServers.braintrust.url` in `.mcp.json` with the URL for your deployment. +`braintrust.url` in `.mcp.json` with the URL for your deployment. Do not enable this plugin and a second connection to the same tools at once. | Data plane | MCP URL | diff --git a/src/plugins/claude/validate.sh b/src/plugins/claude/validate.sh index 2b95a070..7e38a8bf 100755 --- a/src/plugins/claude/validate.sh +++ b/src/plugins/claude/validate.sh @@ -91,8 +91,9 @@ assert marketplace["version"] == manifest["version"], "Claude catalog version mu assert not any("version" in entry for entry in marketplace["plugins"]) # No headers or credentials: Claude Code discovers OAuth from the server. +# Plugin-root .mcp.json is the server map itself, with no mcpServers wrapper. assert json.loads((root / ".mcp.json").read_text()) == { - "mcpServers": {"braintrust": {"type": "http", "url": "https://api.braintrust.dev/mcp"}}, + "braintrust": {"type": "http", "url": "https://api.braintrust.dev/mcp"}, } # Anthropic directory listing icon: square PNG, 512..2048px, under 2 MB.