Skip to content

Commit 98b5a40

Browse files
TMHSDigitalclaude
andcommitted
feat(plugin): make the live Blender session an opt-in second plugin
Since 4be2cea every install of the core plugin launched the Blender Lab MCP server at session start: an error on every session without uv, a duplicate blender server for anyone who had registered it themselves, and nothing at all on 4.5 LTS, where the add-on cannot run. The server now lives in live-session/, a second plugin in the same marketplace, blender-live-session (Claude Code installs it from plugin-dist through a git-subdir source; Cursor from ./live-session). The core manifests declare no mcpServers, and check_manifest_meta and the dist build fail if one comes back. The pin now covers the dependencies too: uvx gets --exclude-newer set to the day after the reviewed commit, because uvx ignores the upstream uv.lock and would otherwise resolve mcp, pyyaml and docutils fresh on each install. mcp-server.yml runs tests/check_mcp_server.py (install + initialize + tools/list over stdio, no Blender needed) weekly and on every change to live-session/, and opens an issue when a scheduled run fails. Verified locally: 26 tools at the pin; a zeroed SHA exits 1. The plugin also ships the live-blender-session skill, which carries the CLAUDE.md live-vs-headless policy to plugin users: read with the summary and screenshot tools first, save before mutating, return a result dict, and prove anything worth keeping with blender --background. README, landing, llms.txt, ROADMAP, AGENTS and CONTRIBUTING describe the new layout. Closes #483 Closes #484 Closes #485 Refs #491 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
1 parent c415851 commit 98b5a40

27 files changed

Lines changed: 497 additions & 56 deletions

‎.claude-plugin/marketplace.json‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
"ref": "plugin-dist"
1515
},
1616
"version": "0.153.0"
17+
},
18+
{
19+
"name": "blender-live-session",
20+
"description": "Opt-in companion to blender-developer-tools: registers the official Blender Lab MCP server so the agent can inspect and drive a running Blender 5.1+ (needs uv and the MCP add-on), plus a skill for reading the scene before mutating it and proving changes headless",
21+
"source": {
22+
"source": "git-subdir",
23+
"url": "TMHSDigital/Blender-Developer-Tools",
24+
"path": "live-session",
25+
"ref": "plugin-dist"
26+
},
27+
"version": "0.153.0"
1728
}
1829
]
1930
}

‎.claude-plugin/plugin.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"homepage": "https://tmhsdigital.github.io/Blender-Developer-Tools/",
1010
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
1111
"license": "CC-BY-NC-ND-4.0 AND MIT",
12-
"mcpServers": "./mcp.json",
1312
"skills": [
1413
"./claude/skills/"
1514
],

‎.cursor-plugin/marketplace.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
"name": "blender-developer-tools",
1212
"source": "./",
1313
"description": "Blender Python skills and rules for AI agents: correct bpy for Blender 5.2 LTS and 4.5 LTS (Slotted Actions, temp_override, Extensions, bmesh, Geometry Nodes, glTF/FBX export), backed by smoke-tested examples"
14+
},
15+
{
16+
"name": "blender-live-session",
17+
"source": "./live-session",
18+
"description": "Opt-in companion to blender-developer-tools: registers the official Blender Lab MCP server so the agent can inspect and drive a running Blender 5.1+ (needs uv and the MCP add-on), plus a skill for reading the scene before mutating it and proving changes headless"
1419
}
1520
]
1621
}

‎.cursor-plugin/plugin.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"blender-addon",
2020
"blender-python"
2121
],
22-
"mcpServers": "./mcp.json",
2322
"skills": [
2423
"skills/addon-scaffolding/SKILL.md",
2524
"skills/ai-mesh-cleanup/SKILL.md",

‎.github/scripts/plugin-content-changed.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
#
66
# Plugin content is every path scripts/build_plugin_dist.py ships (its
77
# --list-paths: skills/, claude/, rules/, snippets/, templates/, the manifests,
8-
# mcp.json, LICENSE), so the list has one owner (#494). release.yml cuts a
8+
# live-session/, LICENSE), so the list has one owner (#494). release.yml cuts a
99
# patch release when this says yes even if no commit subject is feat:/fix:, so
10-
# a `docs:` correction to a skill or a pin bump in mcp.json still reaches
10+
# a `docs:` correction to a skill or an MCP pin bump in live-session/ reaches
1111
# plugin users (#350). Exit 2 if the path list cannot be read. PYTHON
1212
# overrides the interpreter (default python3).
1313
set -euo pipefail

‎.github/scripts/release_notes.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
- **For agents and users**: commits that touched what the plugin ships
1010
(scripts/build_plugin_dist.py DIRS + FILES: skills/, rules/, snippets/,
11-
templates/, claude/, the manifests, mcp.json, LICENSE), grouped Features, Fixes,
11+
templates/, claude/, live-session/, the manifests, LICENSE), grouped Features, Fixes,
1212
Other. This is what a plugin user gets from the update, so it leads.
1313
- **Maintenance**: everything else (CI, tests, the site and gallery,
1414
showcase pieces, examples, docs), in a collapsed <details> block.
@@ -38,7 +38,7 @@
3838
SKIP = re.compile(r"^chore: bump version to ", re.I)
3939
# What the plugin ships (scripts/build_plugin_dist.py DIRS + FILES, #494): a
4040
# change here is a change a plugin user receives, including the manifests and
41-
# mcp.json.
41+
# the opt-in live-session plugin.
4242
USER_DIRS = tuple(d + "/" for d in DIRS)
4343
USER_FILES = frozenset(FILES)
4444
NO_USER_CHANGES = ("No changes to the plugin content (skills, rules, snippets, templates, "

‎.github/workflows/mcp-server.yml‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Pinned MCP server check
2+
3+
# The opt-in blender-live-session plugin launches the Blender Lab MCP server
4+
# from live-session/mcp.json: uvx, a full commit pin, and --exclude-newer so
5+
# its dependencies resolve to what was reviewed with that pin (#484). This
6+
# runs that exact command and speaks MCP over stdio (initialize, tools/list).
7+
# No Blender is needed: listing tools does not call them. It goes red when the
8+
# pin stops resolving (upstream history rewrite, repo move) or the server stops
9+
# starting, before plugin users find out.
10+
11+
on:
12+
workflow_dispatch:
13+
schedule:
14+
- cron: "0 8 * * 2" # weekly, Tuesday 08:00 UTC
15+
push:
16+
branches: [main]
17+
paths:
18+
- "live-session/**"
19+
- "tests/check_mcp_server.py"
20+
- ".github/workflows/mcp-server.yml"
21+
pull_request:
22+
branches: [main]
23+
paths:
24+
- "live-session/**"
25+
- "tests/check_mcp_server.py"
26+
- ".github/workflows/mcp-server.yml"
27+
28+
permissions:
29+
contents: read
30+
31+
jobs:
32+
handshake:
33+
name: Install the pinned server and list its tools
34+
runs-on: ubuntu-latest
35+
timeout-minutes: 15
36+
steps:
37+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
38+
39+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
40+
with:
41+
python-version: "3.12"
42+
43+
- name: Install uv
44+
run: python -m pip install "uv==0.8.5"
45+
46+
- name: Handshake with the server from live-session/mcp.json
47+
run: python tests/check_mcp_server.py --timeout 300
48+
49+
report-scheduled-failure:
50+
name: Report a failed weekly check
51+
needs: handshake
52+
if: always() && github.event_name == 'schedule' && needs.handshake.result != 'success'
53+
runs-on: ubuntu-latest
54+
timeout-minutes: 5
55+
permissions:
56+
issues: write
57+
steps:
58+
- name: Open or update the tracking issue
59+
env:
60+
GH_TOKEN: ${{ github.token }}
61+
GH_REPO: ${{ github.repository }}
62+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
63+
run: |
64+
set -euo pipefail
65+
title="Pinned MCP server no longer installs or starts"
66+
body="Scheduled run: $RUN_URL
67+
68+
live-session/mcp.json no longer answers tools/list. Check the upstream repo at the pinned SHA and the dependency resolution under --exclude-newer, then bump or fix the pin (CLAUDE.md, Live MCP vs Headless Harness)."
69+
num=$(gh issue list --state open --search "\"$title\" in:title" --json number --jq '.[0].number // empty')
70+
if [ -n "$num" ]; then
71+
gh issue comment "$num" --body "$body"
72+
else
73+
gh issue create --title "$title" --body "$body" --label "bug,ci,priority: high"
74+
fi

‎.github/workflows/release.yml‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ jobs:
187187
188188
# Claude Code manifests: same version, rewritten via JSON.
189189
import json
190-
for path in ('.claude-plugin/plugin.json', '.claude-plugin/marketplace.json'):
190+
# live-session/ is the opt-in blender-live-session plugin (#483).
191+
for path in ('.claude-plugin/plugin.json', '.claude-plugin/marketplace.json',
192+
'live-session/.claude-plugin/plugin.json',
193+
'live-session/.cursor-plugin/plugin.json'):
191194
data = json.load(open(path, encoding='utf-8'))
192195
if 'version' in data:
193196
data['version'] = os.environ['NEW_VERSION']
@@ -217,7 +220,8 @@ jobs:
217220
218221
# Explicit paths only: the files this workflow owns, never `git add -A`.
219222
git add -- VERSION CHANGELOG.md CLAUDE.md ROADMAP.md \
220-
.cursor-plugin/plugin.json .claude-plugin/plugin.json .claude-plugin/marketplace.json
223+
.cursor-plugin/plugin.json .claude-plugin/plugin.json .claude-plugin/marketplace.json \
224+
live-session/.claude-plugin/plugin.json live-session/.cursor-plugin/plugin.json
221225
if [ -n "$(git status --porcelain)" ]; then
222226
echo "::warning::files changed that the release commit does not own:"
223227
git status --porcelain

‎.github/workflows/validate.yml‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,24 @@ jobs:
214214
for key in sorted(set(market) - {'name', 'owner', 'metadata', 'plugins'}):
215215
errors.append(f'marketplace.json: {key!r} is not in the Cursor marketplace schema')
216216
entries = market.get('plugins') or []
217-
if [e.get('name') for e in entries] != [manifest.get('name')]:
218-
errors.append('marketplace.json: must list exactly this plugin by name')
217+
live = json.load(open('live-session/.cursor-plugin/plugin.json'))
218+
if [e.get('name') for e in entries] != [manifest.get('name'), live.get('name')]:
219+
errors.append('marketplace.json: must list this plugin, then the opt-in live-session plugin')
219220
for e in entries:
220221
for key in sorted(set(e) - {'name', 'source', 'description', 'minClientVersions'}):
221222
errors.append(f'marketplace.json plugin entry: {key!r} not allowed')
223+
if len(entries) > 1 and entries[1].get('source') != './live-session':
224+
errors.append('marketplace.json: the live-session entry source must be ./live-session')
225+
226+
# The opt-in live-session plugin (#483): same schema, same VERSION.
227+
for key in sorted(set(live) - allowed):
228+
errors.append(f'live-session plugin.json: {key!r} is not in the Cursor plugin schema')
229+
if live.get('version') != version:
230+
errors.append(f"live-session plugin.json version {live.get('version')!r} != VERSION {version!r}")
231+
live_skills = sorted(p.replace('\\', '/').removeprefix('live-session/')
232+
for p in glob.glob('live-session/skills/*/SKILL.md'))
233+
if sorted(live.get('skills', [])) != live_skills:
234+
errors.append(f"live-session plugin.json skills {live.get('skills')} != on disk {live_skills}")
222235
223236
# Every showcase piece names a category the gallery build knows.
224237
sys.path.insert(0, 'scripts')
@@ -265,14 +278,23 @@ jobs:
265278
errors.append(f"claude plugin.json version {plugin.get('version')!r} != VERSION {version!r}")
266279
if plugin.get('name') != 'blender-developer-tools':
267280
errors.append('claude plugin.json name must be blender-developer-tools')
281+
live = json.load(open('live-session/.claude-plugin/plugin.json', encoding='utf-8'))
268282
entries = market.get('plugins', [])
269-
if len(entries) != 1 or entries[0].get('name') != plugin.get('name'):
270-
errors.append('marketplace.json must list exactly the one plugin from plugin.json')
271-
elif entries[0].get('version') != version:
272-
errors.append(f"marketplace.json plugin version {entries[0].get('version')!r} != VERSION {version!r}")
273-
elif entries[0].get('source') != {'source': 'github', 'repo': 'TMHSDigital/Blender-Developer-Tools', 'ref': 'plugin-dist'}:
274-
errors.append('marketplace.json plugin source must be the plugin-dist branch '
275-
'(github TMHSDigital/Blender-Developer-Tools, ref plugin-dist)')
283+
if [e.get('name') for e in entries] != [plugin.get('name'), live.get('name')]:
284+
errors.append('marketplace.json must list the core plugin, then blender-live-session')
285+
else:
286+
want_sources = (
287+
{'source': 'github', 'repo': 'TMHSDigital/Blender-Developer-Tools', 'ref': 'plugin-dist'},
288+
{'source': 'git-subdir', 'url': 'TMHSDigital/Blender-Developer-Tools',
289+
'path': 'live-session', 'ref': 'plugin-dist'},
290+
)
291+
for entry, want in zip(entries, want_sources):
292+
if entry.get('version') != version:
293+
errors.append(f"marketplace.json {entry['name']} version {entry.get('version')!r} != VERSION {version!r}")
294+
if entry.get('source') != want:
295+
errors.append(f"marketplace.json {entry['name']} source must be {want} (the plugin-dist branch)")
296+
if live.get('version') != version:
297+
errors.append(f"live-session claude plugin.json version {live.get('version')!r} != VERSION {version!r}")
276298
277299
# Claude Code discovers skills/<name>/SKILL.md plus every extra
278300
# directory in plugin.json "skills" (the generated blender-rules

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repeats the other.
1515

1616
Skills, rules, snippets, starter templates, and runnable smoke-gated examples
1717
for Blender Python development. The repo targets **Blender 5.2 LTS** (current
18-
stable) with a **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. The repo ships no MCP server of its own; the plugin's `mcp.json` registers the upstream Blender Lab server (pinned commit, launched with `uvx`) for optional live sessions. It ships
18+
stable) with a **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. The repo ships no MCP server of its own; the opt-in `blender-live-session` plugin (`live-session/`) registers the upstream Blender Lab server (pinned commit and `--exclude-newer`, launched with `uvx`) for live sessions. It ships
1919
a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker
2020
classifies it as a `cursor-plugin`. This is content the AI loads when the user
2121
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.

0 commit comments

Comments
 (0)