สั่ง Blender ผ่าน MCP ให้ทุกการแก้ฉากเป็นสคริปต์ที่ตรวจย้อนหลังได้
คำสั่งที่ AI ส่งเข้าโปรแกรมที่เปิดอยู่เป็นคำสั่งที่แก้ของจริงได้ทุกอย่าง ผมเลยอยากให้มันเหลือหลักฐานเป็นไฟล์ทุกครั้ง ไม่ใช่หายไปกับประวัติแชต
สรุปใน 1 นาที
- อาการ/ปัญหา
- ต้องการให้ AI client สั่ง Blender 4.5 ผ่าน MCP โดยไม่ปล่อยให้ส่งโค้ดสดจากแชตเข้าฉาก
- ใครควรอ่าน
- คนที่จะต่อ AI เข้ากับ Blender ที่เปิดอยู่ และอยากให้คนตรวจโค้ดก่อนรัน
- ทางแก้แบบสั้น
- ทุกการเปลี่ยนฉากเป็นไฟล์สคริปต์หนึ่งไฟล์ให้คนอ่านก่อน แล้วส่งด้วย run_script.py ที่เก็บผลเป็น .execution.json กับ .mcp.log ข้างสคริปต์
พอดีผมตั้งค่าให้ AI client แบบ CLI ที่รองรับ MCP สั่ง Blender 4.5 ที่เปิดค้างอยู่บนเครื่องได้ ตัวเชื่อมใช้ mcp-for-blender รุ่น 1.30.0 ที่รันผ่าน uvx ตอนตั้งเสร็จแล้วเครื่องมือชื่อ execute_blender_code รันโค้ด Python ของ bpy ได้ทุกอย่างที่ Blender ทำได้ ซึ่งแปลว่ามันแก้ฉากและแก้ไฟล์จริงได้เหมือนผมนั่งพิมพ์ใน Python console เอง ผมเลยไม่อยากปล่อยให้โค้ดสด ๆ จากแชตวิ่งเข้าไปตรง ๆ แล้วตั้งกติกาว่า หนึ่งการเปลี่ยนฉากต้องเป็นหนึ่งไฟล์สคริปต์ ที่ผมอ่านก่อนรัน และมีผลลัพธ์เก็บเป็นไฟล์ติดกัน บทความนี้เล่าทั้งตัวเชื่อม ตัวส่งสคริปต์ 26 บรรทัด และปัญหาที่เจอตอนติดตั้งจริงหกข้อ
ผมจงใจไม่เล่าว่าสร้างอะไรในฉาก เพราะเรื่องนั้นไม่เกี่ยวกับวิธีเชื่อมต่อ เนื้อหานี้ล้วนเป็นเรื่องท่อส่งคำสั่งกับการเก็บหลักฐาน
เส้นทางเชื่อมต่อ ทุกอย่างอยู่ในเครื่องเดียว
เส้นทางจากแชตถึงฉากมีสี่ช่วง AI client คุยกับตัวเชื่อมแบบ STDIO (client เปิดตัวเชื่อมเป็นโปรเซสลูกแล้วคุยผ่าน stdin/stdout) ตัวเชื่อมรันด้วย Python 3.11 ที่ uv จัดการให้เอง แล้วต่อ TCP เข้า addon ที่ฟังอยู่ในหน้าต่าง Blender ที่พอร์ต 9876 และ addon เป็นฝ่ายเรียก bpy ภายใน Blender ไม่มี HTTP ไม่มี CDP ในเส้นทางนี้เลย
ค่าที่ผมใส่ในคอนฟิกของ client เป็นแบบนี้ รูปแบบไฟล์จะต่างกันไปตาม client แต่ค่าเหมือนกัน
[mcp_servers.blender]
command = "uvx"
args = ["--python", "3.11", "mcp-for-blender"]
startup_timeout_sec = 60
tool_timeout_sec = 180
env = { BLENDER_HOST = "127.0.0.1", BLENDER_PORT = "9876", UV_PYTHON_PREFERENCE = "only-managed", PYTHONUTF8 = "1" }
ส่วน addon ติดตั้งด้วยคำสั่งของตัวเชื่อมเอง ชี้ไปที่โฟลเดอร์ scripts/addons ของ Blender 4.5 จากนั้นเปิดใช้ใน Preferences แล้วเข้าแผง N ใน 3D View กด Start MCP Server (บางรุ่นเริ่มให้เองอัตโนมัติ)
$env:PYTHONUTF8 = '1'
uvx --python 3.11 mcp-for-blender install-addon --addons-dir "<โฟลเดอร์ scripts/addons ของ Blender 4.5>"
พอ client เรียก list_tools จะเจอเครื่องมือเจ็ดตัวกับเครื่องมือ telemetry อีกกลุ่ม คือ get_addon_status, get_scene_info, execute_blender_code, look, generate_3d, search_assets, import_asset ผมปิด telemetry consent ไว้ และใช้งานจริงแค่ execute_blender_code ตัวเดียว ตัวอื่นไม่ได้แตะ
ทำไมไม่ให้ AI ส่งโค้ดสดเข้า Blender
execute_blender_code รับสตริงโค้ด Python แล้วรันใน Blender ตรง ๆ โค้ดนั้นลบวัตถุ เปลี่ยนไฟล์ .blend หรือสั่งบันทึกทับก็ได้ และเพราะมันเป็น Python เต็มรูปแบบ มันทำได้ทุกอย่างที่โปรเซส Blender มีสิทธิ์ทำ ถ้าปล่อยให้โค้ดไหลจากแชตเข้ามาตรง ๆ ผมจะเหลือแค่ข้อความใน scrollback ที่ต้องมานั่งเดาว่าเมื่อวานสั่งอะไรไปบ้าง

กติกาที่ผมเลือกเลยเป็นแบบนี้ AI เขียนไฟล์สคริปต์ลงโฟลเดอร์งาน ผมเปิดอ่านจริง ๆ แล้วค่อยรันผ่านตัวส่งที่ผมคุมเอง ได้ข้อดีสามอย่างพร้อมกัน
เร็ว แต่ไม่เหลืออะไร
ไม่มีจุดให้คนอ่านก่อนรัน ไม่มีไฟล์ให้ย้อนดูว่าสั่งอะไรเมื่อไร และถ้าผลออกมาผิด การรันซ้ำให้เหมือนเดิมแทบเป็นไปไม่ได้เพราะต้นทางคือข้อความที่ผ่านไปแล้ว
ช้าลงนิดเดียว ได้ประวัติ
มีไฟล์ให้อ่านก่อน มี .execution.json บอกว่ารันสำเร็จหรือไม่ มี .mcp.log ของตัวเชื่อม รันซ้ำได้ด้วยไฟล์เดิม และเห็นประวัติการเปลี่ยนฉากเรียงเป็นชุดไฟล์
โค้ด bpy แก้ไฟล์ได้จริง และ addon เปิดพอร์ตรับคำสั่งรันโค้ด ถ้าพอร์ตนั้นรับจากเครือข่ายได้ ใครต่อเข้ามาได้ก็สั่งแก้เครื่องได้ ผมเลยตรวจให้แน่ว่าฟังที่ 127.0.0.1 เท่านั้น และให้คนเป็นด่านสุดท้ายก่อนทุกการรัน ไม่ใช่เชื่อโค้ดเพียงเพราะมันมาจากเครื่องมือของเราเอง
run_script.py ตัวส่ง 26 บรรทัด
ตัวส่งเป็นสคริปต์ Python สั้น ๆ ที่ทำหน้าที่เป็น MCP client เอง ไม่ต้องพึ่งแชต มันเปิดตัวเชื่อมแบบ STDIO ส่ง stderr ของตัวเชื่อมลงไฟล์ .mcp.log เรียก initialize แล้ว call_tool ด้วยเนื้อไฟล์ เก็บผลทั้งก้อนเป็น .execution.json และปิดท้ายด้วย assert ว่า isError เป็นเท็จ ผมเขียนใหม่ให้สั้นลงเล็กน้อยจากของจริง
"""รันสคริปต์ที่ตรวจแล้วหนึ่งไฟล์ผ่าน STDIO bridge ของ Blender MCP"""
import asyncio, json, os, sys
from pathlib import Path
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
script = Path(sys.argv[1]).resolve()
assert script.is_file()
env = dict(os.environ, BLENDER_HOST='127.0.0.1', BLENDER_PORT='9876',
PYTHONUTF8='1', UV_PYTHON_PREFERENCE='only-managed')
params = StdioServerParameters(
command='uvx', args=['--python', '3.11', 'mcp-for-blender'], env=env)
with script.with_suffix('.mcp.log').open('w', encoding='utf-8') as errlog:
async with stdio_client(params, errlog=errlog) as (r, w):
async with ClientSession(r, w) as session:
await session.initialize()
code = script.read_text(encoding='utf-8')
result = await session.call_tool('execute_blender_code', {'code': code})
out = script.with_suffix('.execution.json')
out.write_text(json.dumps(result.model_dump(mode='json'),
ensure_ascii=False, indent=2), encoding='utf-8')
for block in result.content:
if hasattr(block, 'text'):
print(block.text)
assert not result.isError, 'Blender ตอบ error ดูใน .execution.json'
asyncio.run(main())
ที่ต้องรันผ่าน uvx --from mcp-for-blender python run_script.py ... เพราะไลบรารี mcp ที่สคริปต์ import อยู่ในสภาพแวดล้อมของแพ็กเกจนั้นอยู่แล้ว ไม่ต้องลงเพิ่มในระบบ
$env:PYTHONUTF8 = '1'
uvx --python 3.11 --from mcp-for-blender python run_script.py .\changes\0001-first-change.py
- ให้ AI เขียนไฟล์
0001-first-change.pyลงโฟลเดอร์changesพร้อมคอมเมนต์ที่บอกว่าสคริปต์นี้ทำอะไร - ผมเปิดอ่านทั้งไฟล์ ดูว่ามีอะไรแตะนอกฉากไหม เช่นการเขียนไฟล์ การเรียกโปรเซส หรือการบันทึกทับ
- รันด้วยตัวส่งด้านบน ได้
.execution.jsonกับ.mcp.logติดกับสคริปต์ - เปิดดู
isErrorและข้อความผลลัพธ์ ถ้าผิดก็แก้สคริปต์เป็นไฟล์ใหม่0002-...ไม่ไปแก้ทับไฟล์เดิม - เก็บทั้งชุดไว้เป็นประวัติ ย้อนดูหรือรันซ้ำเมื่อไรก็ได้
ข้อสุดท้ายนี่แหละที่ผมให้ค่ามากสุด ถ้าสคริปต์ไหนทำฉากเพี้ยน ผมรู้ทันทีว่าไฟล์ไหน รันเมื่อไร และได้ผลอะไรกลับมา เพราะมีทั้งสามไฟล์เป็นหลักฐาน
verify_mcp.py ทดสอบการเชื่อมต่อแบบอ่านอย่างเดียว
ก่อนใช้งานจริงผมมีสคริปต์ทดสอบการเชื่อมอีกตัว โครงเหมือนตัวส่ง ต่างกันที่มันเรียก list_tools ก่อน ตรวจว่า execute_blender_code อยู่ในรายการ แล้วรันโค้ดอ่านอย่างเดียวสั้น ๆ ที่พิมพ์ JSON ของเวอร์ชัน Blender ชื่อ scene จำนวนวัตถุ ว่า addon เปิดอยู่ไหม และ telemetry consent เป็นอะไร ไม่แก้อะไรในฉากเลย
code = '''
import bpy, json
print(json.dumps({
"version": bpy.app.version_string,
"scene": bpy.context.scene.name,
"objects": len(bpy.context.scene.objects),
"addon_enabled": "blender_mcp" in bpy.context.preferences.addons,
"telemetry_consent":
bpy.context.preferences.addons["blender_mcp"].preferences.telemetry_consent,
}))
'''
names = [t.name for t in (await session.list_tools()).tools]
assert 'execute_blender_code' in names, names
reply = await session.call_tool('execute_blender_code', {'code': code})
assert not reply.isError, reply
ผลเขียนเป็น connection-test.json มีฟิลด์ passed: true ที่ผมเก็บไว้เป็นหลักฐานว่าเส้นทางทั้งสี่ช่วงต่อถึงกันจริงตอนติดตั้ง ไม่ใช่แค่เห็นพอร์ตเปิด

หน้าตาของ .execution.json เป็นผลของ model_dump ของ CallToolResult ในไลบรารี mcp ประมาณนี้ (ตัวอย่างโครง ไม่ใช่ผลจริงของฉากใด)
{
"content": [
{ "type": "text", "text": "{\"version\": \"4.5.0\", \"scene\": \"Scene\", \"objects\": 3}" }
],
"isError": false
}
สคริปต์ที่ผ่านการอ่านหน้าตาเป็นอย่างไร
ผมตั้งนิสัยให้ทุกสคริปต์สั้น ทำเรื่องเดียว และขึ้นต้นด้วยคอมเมนต์สามบรรทัดบอกว่าจะทำอะไร แตะอะไร และถ้าผิดจะย้อนอย่างไร สคริปต์ที่ยาวเป็นร้อยบรรทัดและทำห้าเรื่องพร้อมกันอ่านยากจนการตรวจก่อนรันกลายเป็นพิธีกรรม ตัวอย่างโครงที่ผมใช้ (ชื่อวัตถุเป็นชื่อสมมติ)
# 0002-rename-group.py
# ทำอะไร : เปลี่ยนชื่อ collection หนึ่งตัว
# แตะอะไร: เฉพาะ bpy.data.collections ไม่เขียนไฟล์ ไม่เรียกโปรเซสอื่น
# ย้อนอย่างไร: เปลี่ยนชื่อกลับด้วยค่า OLD/NEW สลับกัน
import bpy
OLD, NEW = "Group_A", "Group_B"
col = bpy.data.collections.get(OLD)
assert col is not None, f"ไม่พบ collection {OLD}"
col.name = NEW
print({"renamed": OLD, "to": col.name})
สังเกตว่ามี assert ตรวจเงื่อนไขก่อนแตะอะไร ถ้าชื่อเดิมไม่มี สคริปต์ล้มตรงนั้นและ isError เป็นจริง ตัวส่งก็ล้มตาม ผมไม่ต้องไปเดาว่ารันผ่านหรือเปล่า ส่วนสิ่งที่ผมไล่ดูตอนอ่านสคริปต์มีไม่กี่ข้อ
มี import ที่ไม่น่าจะต้องใช้ไหม เช่น os subprocess shutil มีการเขียนหรือลบไฟล์นอกฉากไหม มี bpy.ops.wm.save_mainfile หรือบันทึกทับไฟล์ที่ไม่ได้ตั้งใจไหม และมีเงื่อนไขตรวจก่อนแก้หรือเปล่า ถ้าข้อไหนไม่ชัดผมให้ AI อธิบายเป็นคอมเมนต์เพิ่มก่อนรัน ไม่รันไปก่อนแล้วค่อยดู
ค่า timeout สองตัวในคอนฟิกก็มีความหมายต่อการใช้งานจริง startup_timeout_sec = 60 คือเวลาที่ client รอให้ตัวเชื่อมเริ่มเสร็จ ซึ่งรอบแรก uvx อาจต้องเตรียม Python 3.11 และแพ็กเกจให้ก่อน ส่วน tool_timeout_sec = 180 คือเวลาสูงสุดต่อหนึ่งการเรียกเครื่องมือ สคริปต์ที่ใช้เวลานานกว่านั้นควรแตกเป็นหลายไฟล์ ซึ่งสอดคล้องกับกติกาหนึ่งสคริปต์ต่อหนึ่งการเปลี่ยนอยู่แล้ว
ส่วนโฟลเดอร์งาน ผมใช้เลขนำหน้าชื่อไฟล์เรียงตามลำดับที่รัน เพื่อให้ ls เรียงเป็นประวัติได้เลยโดยไม่ต้องมีดัชนีแยก
changes/
0001-first-change.py
0001-first-change.execution.json
0001-first-change.mcp.log
0002-rename-group.py
0002-rename-group.execution.json
0002-rename-group.mcp.log
ปัญหาที่เจอจริงหกข้อ
ตอนติดตั้งผมเจอปัญหาที่ไม่ได้อยู่ในคู่มือสั้น ๆ ของตัวเชื่อมหกข้อ เรียงตามที่เจอ
- ข้อความ Repository data ... blender_org not found, sync required ต้องซิงก์คลัง extension ของ Blender ก่อน แล้วตรวจว่ามีไฟล์ดัชนีอยู่จริง
- UnicodeEncodeError cp874 ตอน install-addon บน Windows ที่ตั้งภาษาไทย ตัวติดตั้งพิมพ์ข้อความที่โค้ดเพจ cp874 รับไม่ได้ ต้องตั้ง
PYTHONUTF8=1ก่อนรัน ข้อควรระวังคือไฟล์ addon อาจถูกคัดลอกสำเร็จไปแล้วทั้งที่คำสั่งล้มตอนพิมพ์ผล เลยตรวจไฟล์ก่อนรันซ้ำ - Connection refused Blender ต้องเปิดอยู่และ addon ต้องฟังพอร์ต 9876 อยู่จริง
- แก้คอนฟิกแล้วเครื่องมือไม่โผล่ในแชต ต้องเปิด client ใหม่ ระหว่างนั้นใช้ตัวส่งผ่าน STDIO ได้เลยโดยไม่ต้องรอ
- ห้ามเปิด Blender หลายตัวบนพอร์ตเดียวกัน ผลที่ตามมาคาดเดาไม่ได้ว่าคำสั่งจะไปถึงตัวไหน และอย่าสับสนพอร์ตนี้กับพอร์ต CDP ของเบราว์เซอร์
- นำเข้าโมเดลแล้วบันทึกฉากสำเร็จ แต่หน้าจอ GUI ยังโชว์ลูกบาศก์เริ่มต้นค้างอยู่ สั่งให้ Blender เปิดไฟล์ที่เพิ่งเซฟด้วย
bpy.ops.wm.open_mainfileแล้วฉากแสดงครบ ข้อนี้สาเหตุผมสรุปจากบันทึกการทำงาน น่าจะเป็นฉากเริ่มต้นที่ยังค้างบนหน้าจอ ไม่ได้พิสูจน์เป็นขั้นเป็นตอน
คำสั่งตรวจกับแก้ของสามข้อแรกเป็นแบบนี้
# 1. ซิงก์คลัง extension แล้วตรวจไฟล์ดัชนี
& "<โฟลเดอร์ติดตั้ง>\Blender 4.5\blender.exe" --background --online-mode --command extension sync
Test-Path "$env:APPDATA\Blender Foundation\Blender\4.5\extensions\blender_org\.blender_ext\index.json"
# 2. กัน cp874 ตอนติดตั้ง addon
$env:PYTHONUTF8 = '1'
# 3. ตรวจว่า addon ฟังพอร์ตหรือยัง
Get-NetTCPConnection -LocalPort 9876 -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess
ผลของ Get-NetTCPConnection ต้องเป็น 127.0.0.1 เท่านั้น ถ้าเห็น 0.0.0.0 แปลว่าพอร์ตเปิดรับจากเครือข่าย ซึ่งไม่ใช่ที่ตั้งใจและต้องปิดก่อนใช้งานต่อ ควรเช็กทุกครั้งก่อนเริ่มงานรอบใหม่
สรุปสิ่งที่ผมใช้จริง
ส่วนตัวผมถือว่าเมื่อเครื่องมือ MCP ตัวไหนรันโค้ดได้อิสระแบบ execute_blender_code การมีไฟล์ให้คนอ่านก่อนรันกับผลที่เก็บเป็นไฟล์ คือส่วนที่ขาดไม่ได้ ตัวเชื่อมจะดีแค่ไหนก็ไม่ได้ช่วยตรงนี้ ส่วนจะเขียนตัวส่งด้วยภาษาอื่น หรือเปลี่ยนที่เก็บประวัติเป็น git ตามสะดวก ตามใจชอบได้ ขอแค่ยังได้สามอย่าง คือไฟล์สคริปต์ ไฟล์ผลที่มี isError และ log ของตัวเชื่อมติดกัน
ถ้าอยากเทียบกับการคุมโปรแกรมอื่นด้วยแนวคิดคล้ายกัน ดู คิวไฟล์คุมโปรแกรมใน VM กับ ปลั๊กอิน MCP ของ SketchUp ต่างกันที่เรื่องนี้เน้นรูปแบบหนึ่งการเปลี่ยนฉากต่อหนึ่งไฟล์สคริปต์พร้อมหลักฐาน JSON ตรงไหนผมอธิบายตกหรือรุ่นของตัวเชื่อมเปลี่ยนไปจนคำสั่งไม่ตรง เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
อยากให้เราทำให้ไหม?
ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย