dcc-mcp-sketchup 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. dcc_mcp_sketchup-0.1.0/.github/workflows/ci.yml +75 -0
  2. dcc_mcp_sketchup-0.1.0/.github/workflows/release.yml +68 -0
  3. dcc_mcp_sketchup-0.1.0/.gitignore +8 -0
  4. dcc_mcp_sketchup-0.1.0/.release-please-manifest.json +3 -0
  5. dcc_mcp_sketchup-0.1.0/CHANGELOG.md +13 -0
  6. dcc_mcp_sketchup-0.1.0/LICENSE +22 -0
  7. dcc_mcp_sketchup-0.1.0/PKG-INFO +179 -0
  8. dcc_mcp_sketchup-0.1.0/README.md +145 -0
  9. dcc_mcp_sketchup-0.1.0/docs/architecture.md +54 -0
  10. dcc_mcp_sketchup-0.1.0/pyproject.toml +58 -0
  11. dcc_mcp_sketchup-0.1.0/release-please-config.json +19 -0
  12. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/__init__.py +4 -0
  13. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/__version__.py +1 -0
  14. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/bridge.py +181 -0
  15. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/dispatcher.py +22 -0
  16. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/install.py +119 -0
  17. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/server.py +246 -0
  18. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/sketchup_plugin/commands.rb +808 -0
  19. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/sketchup_plugin/main.rb +25 -0
  20. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/sketchup_plugin/registration.rb +18 -0
  21. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/sketchup_plugin/runtime.rb +504 -0
  22. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skill_tools.py +22 -0
  23. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/SKILL.md +24 -0
  24. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/scripts/assign_material.py +8 -0
  25. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/scripts/create_material.py +8 -0
  26. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/scripts/list_materials.py +8 -0
  27. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/scripts/remove_material.py +8 -0
  28. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/scripts/update_material.py +8 -0
  29. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-materials/tools.yaml +95 -0
  30. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/SKILL.md +25 -0
  31. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/add_box.py +8 -0
  32. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/add_cylinder.py +8 -0
  33. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/erase_entities.py +8 -0
  34. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/group_entities.py +8 -0
  35. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/rename_entity.py +8 -0
  36. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/select_entities.py +8 -0
  37. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/scripts/transform_entity.py +8 -0
  38. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-modeling/tools.yaml +176 -0
  39. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/SKILL.md +24 -0
  40. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/assign_tag.py +8 -0
  41. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/create_scene.py +8 -0
  42. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/create_tag.py +8 -0
  43. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/list_scenes.py +8 -0
  44. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/list_tags.py +8 -0
  45. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/remove_scene.py +8 -0
  46. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/remove_tag.py +8 -0
  47. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/scripts/update_scene.py +8 -0
  48. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-scenes/tools.yaml +124 -0
  49. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/SKILL.md +25 -0
  50. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/export_model.py +8 -0
  51. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/get_status.py +8 -0
  52. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/import_model.py +8 -0
  53. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/inspect_model.py +8 -0
  54. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/list_entities.py +8 -0
  55. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/save_copy.py +8 -0
  56. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/save_model.py +8 -0
  57. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/scripts/validate_model.py +8 -0
  58. dcc_mcp_sketchup-0.1.0/src/dcc_mcp_sketchup/skills/sketchup-session/tools.yaml +134 -0
  59. dcc_mcp_sketchup-0.1.0/tests/ruby/test_commands.rb +372 -0
  60. dcc_mcp_sketchup-0.1.0/tests/ruby/test_runtime.rb +446 -0
  61. dcc_mcp_sketchup-0.1.0/tests/test_bridge.py +112 -0
  62. dcc_mcp_sketchup-0.1.0/tests/test_install.py +66 -0
  63. dcc_mcp_sketchup-0.1.0/tests/test_python_compat.py +16 -0
  64. dcc_mcp_sketchup-0.1.0/tests/test_server.py +42 -0
  65. dcc_mcp_sketchup-0.1.0/tests/test_skills.py +63 -0
@@ -0,0 +1,75 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main, "release-please--branches--**"]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ test:
19
+ name: Test Python ${{ matrix.python }} on ${{ matrix.os }}
20
+ runs-on: ${{ matrix.os }}
21
+ timeout-minutes: 15
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ os: [ubuntu-latest, windows-latest, macos-latest]
26
+ python: ["3.9", "3.10", "3.11", "3.12"]
27
+ steps:
28
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
29
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
30
+ with:
31
+ python-version: ${{ matrix.python }}
32
+ - run: python -m pip install --upgrade pip
33
+ - run: python -m pip install -e ".[test]"
34
+ - run: python -m pytest
35
+
36
+ ruby-extension:
37
+ name: Ruby ${{ matrix.ruby }} extension contract on ${{ matrix.os }}
38
+ runs-on: ${{ matrix.os }}
39
+ timeout-minutes: 10
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ os: [ubuntu-latest, windows-latest]
44
+ ruby: ["2.7", "3.2"]
45
+ steps:
46
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
47
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
48
+ with:
49
+ ruby-version: ${{ matrix.ruby }}
50
+ - name: Check Ruby syntax
51
+ run: >-
52
+ ruby -e 'Dir["{src/dcc_mcp_sketchup/sketchup_plugin,tests/ruby}/**/*.rb"].sort.each { |file| abort("syntax failed: #{file}") unless system(RbConfig.ruby, "-c", file) }'
53
+ - name: Run Ruby contract tests
54
+ run: ruby -e 'Dir["tests/ruby/test_*.rb"].sort.each { |file| require File.expand_path(file) }'
55
+
56
+ lint-and-build:
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 15
59
+ steps:
60
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
61
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
62
+ with:
63
+ python-version: "3.11"
64
+ - run: python -m pip install --upgrade pip
65
+ - run: python -m pip install -e ".[dev]"
66
+ - run: python -m ruff check src tests
67
+ - run: python -m ruff format --check src tests
68
+ - run: python -m build
69
+ - run: python -m twine check dist/*
70
+ - name: Smoke test the built wheel
71
+ shell: bash
72
+ run: |
73
+ python -m pip install --force-reinstall --no-deps dist/*.whl
74
+ python -c "import importlib.metadata as m, importlib.resources as r, dcc_mcp_sketchup as p; assert p.__version__ == m.version('dcc-mcp-sketchup'); root = r.files('dcc_mcp_sketchup'); assert root.joinpath('sketchup_plugin', 'runtime.rb').is_file(); assert root.joinpath('skills', 'sketchup-session', 'SKILL.md').is_file()"
75
+ python -c "import importlib.metadata as m, subprocess; expected = m.version('dcc-mcp-sketchup'); actual = subprocess.check_output(['dcc-mcp-sketchup', '--version'], text=True).strip(); assert actual == expected, (actual, expected)"
@@ -0,0 +1,68 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+ inputs:
8
+ tag_name:
9
+ description: Existing tag to rebuild and publish
10
+ required: false
11
+ default: ""
12
+ publish_to_pypi:
13
+ description: Publish a manually rebuilt tag to PyPI
14
+ type: boolean
15
+ required: false
16
+ default: false
17
+
18
+ concurrency:
19
+ group: ${{ github.workflow }}-${{ github.ref }}
20
+ cancel-in-progress: false
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ jobs:
26
+ release-please:
27
+ runs-on: ubuntu-latest
28
+ timeout-minutes: 10
29
+ if: github.ref == 'refs/heads/main'
30
+ permissions:
31
+ contents: write
32
+ pull-requests: write
33
+ outputs:
34
+ release_created: ${{ steps.release.outputs.release_created }}
35
+ tag_name: ${{ steps.release.outputs.tag_name || inputs.tag_name }}
36
+ steps:
37
+ - uses: googleapis/release-please-action@0dfd8538845b8e92600d271a895a5372865d4062 # v5
38
+ id: release
39
+ with:
40
+ token: ${{ github.token }}
41
+ config-file: release-please-config.json
42
+ manifest-file: .release-please-manifest.json
43
+
44
+ build-and-publish:
45
+ needs: release-please
46
+ if: needs.release-please.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '' && inputs.publish_to_pypi)
47
+ runs-on: ubuntu-latest
48
+ timeout-minutes: 15
49
+ environment:
50
+ name: pypi
51
+ url: https://pypi.org/p/dcc-mcp-sketchup
52
+ permissions:
53
+ contents: read
54
+ id-token: write
55
+ steps:
56
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
57
+ with:
58
+ ref: ${{ needs.release-please.outputs.tag_name || inputs.tag_name }}
59
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
60
+ with:
61
+ python-version: "3.11"
62
+ - run: python -m pip install --upgrade pip build twine
63
+ - run: python -m build
64
+ - run: python -m twine check dist/*
65
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
66
+ with:
67
+ verbose: true
68
+ print-hash: true
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.0"
3
+ }
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-08-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * add experimental SketchUp adapter ([29a4ee5](https://github.com/dcc-mcp/dcc-mcp-sketchup/commit/29a4ee5178263c84914d17a659f81a3f91d614a6))
9
+ * ship production-ready SketchUp adapter ([#1](https://github.com/dcc-mcp/dcc-mcp-sketchup/issues/1)) ([1cc2fcc](https://github.com/dcc-mcp/dcc-mcp-sketchup/commit/1cc2fcc1b28f5531f3fe344e86c20ab414300b73))
10
+
11
+ ## Changelog
12
+
13
+ All notable changes to this project will be documented in this file by Release Please.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 loonghao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.5
2
+ Name: dcc-mcp-sketchup
3
+ Version: 0.1.0
4
+ Summary: Production SketchUp adapter with an authenticated Ruby main-thread bridge for DCC-MCP
5
+ Project-URL: Homepage, https://github.com/dcc-mcp/dcc-mcp-sketchup
6
+ Project-URL: Repository, https://github.com/dcc-mcp/dcc-mcp-sketchup
7
+ Project-URL: Issues, https://github.com/dcc-mcp/dcc-mcp-sketchup/issues
8
+ Author-email: loonghao <hal.long@outlook.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: 3d-modeling,automation,cad,dcc,mcp,sketchup
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: dcc-mcp-core<1.0.0,>=0.19.91
23
+ Provides-Extra: dev
24
+ Requires-Dist: build>=1.2; extra == 'dev'
25
+ Requires-Dist: pytest>=8; extra == 'dev'
26
+ Requires-Dist: pyyaml>=6; extra == 'dev'
27
+ Requires-Dist: ruff>=0.8; extra == 'dev'
28
+ Requires-Dist: twine>=6; extra == 'dev'
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=8; extra == 'test'
31
+ Requires-Dist: pyyaml>=6; extra == 'test'
32
+ Requires-Dist: ruff>=0.8; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # dcc-mcp-sketchup
36
+
37
+ <p align="center">
38
+ <picture>
39
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/dcc-mcp-sketchup-dark.svg">
40
+ <source media="(prefers-color-scheme: light)" srcset="docs/assets/dcc-mcp-sketchup.svg">
41
+ <img src="docs/assets/dcc-mcp-sketchup.svg" alt="DCC-MCP · SKETCHUP" width="600">
42
+ </picture>
43
+ </p>
44
+
45
+ Production SketchUp adapter for the [DCC-MCP](https://github.com/dcc-mcp) ecosystem.
46
+ It combines a small Ruby extension inside SketchUp with an external Python
47
+ sidecar, exposing 28 typed tools without arbitrary Ruby execution.
48
+
49
+ ![Typed SketchUp model inspection, construction, organization, validation, and interchange export](docs/images/sketchup-showcase.webp)
50
+
51
+ <sub>Workflow illustration generated with OpenAI image generation; no third-party source assets.</sub>
52
+
53
+ ## Capabilities
54
+
55
+ - Inspect model identity, bounds, entities, selection, and validation state.
56
+ - Save or copy `.skp` models and use installed SketchUp importers/exporters.
57
+ - Create boxes and cylinders, group, transform, rename, select, and erase entities.
58
+ - List, create, edit, assign, and safely remove materials.
59
+ - Manage saved scenes and Tags.
60
+ - Reference entities through SketchUp persistent IDs.
61
+ - Package four discoverable DCC-MCP Skills with complete JSON Schemas and MCP annotations.
62
+
63
+ ## Architecture
64
+
65
+ ```text
66
+ DCC-MCP client
67
+ |
68
+ v
69
+ external Python sidecar (dcc-mcp-core)
70
+ |
71
+ | authenticated, bounded JSON-RPC on 127.0.0.1
72
+ v
73
+ UI.start_timer callback on SketchUp's UI thread
74
+ |
75
+ | nonblocking, bounded socket pump + one-request queue
76
+ |
77
+ v
78
+ typed SketchUp Ruby API command map
79
+ ```
80
+
81
+ The Ruby extension has a single socket owner: the repeating UI timer. Each tick
82
+ uses zero-timeout `IO.select` and nonblocking accept, read, and write operations
83
+ across at most 16 connections, then executes at most one validated request.
84
+ Frame, response, connection, and deadline limits keep every tick bounded while
85
+ preserving SketchUp's thread affinity. No worker thread performs socket I/O or
86
+ calls the SketchUp API. Every model mutation is a named undoable operation.
87
+
88
+ ## Requirements
89
+
90
+ - SketchUp Desktop 2021 or newer on Windows or macOS.
91
+ - Python 3.9 or newer for the external sidecar.
92
+ - `dcc-mcp-core>=0.19.91,<1.0.0` (installed automatically).
93
+
94
+ Importer and exporter availability varies by SketchUp edition, version, and
95
+ installed extensions. The adapter reports the host error instead of claiming a
96
+ format is available when SketchUp rejects it.
97
+
98
+ ## Install
99
+
100
+ Install the package into the Python environment used by DCC-MCP:
101
+
102
+ ```bash
103
+ python -m pip install dcc-mcp-sketchup
104
+ ```
105
+
106
+ Start SketchUp once so its versioned user profile exists, then install the Ruby
107
+ extension:
108
+
109
+ ```bash
110
+ dcc-mcp-sketchup install
111
+ ```
112
+
113
+ For a specific SketchUp profile, pass its Plugins directory explicitly:
114
+
115
+ ```bash
116
+ dcc-mcp-sketchup install \
117
+ --plugins-dir "C:/Users/you/AppData/Roaming/SketchUp/SketchUp 2026/SketchUp/Plugins"
118
+ ```
119
+
120
+ Restart SketchUp. The extension binds an ephemeral loopback port, generates a
121
+ random token, and launches the host-bound sidecar automatically. It terminates
122
+ the sidecar when SketchUp exits.
123
+
124
+ To update or remove only files owned by this package:
125
+
126
+ ```bash
127
+ dcc-mcp-sketchup install --overwrite
128
+ dcc-mcp-sketchup uninstall
129
+ ```
130
+
131
+ ## Skills and tools
132
+
133
+ | Skill | Tools |
134
+ | --- | --- |
135
+ | `sketchup-session` | status, inspection, root entities, save, copy, validate, import, export |
136
+ | `sketchup-modeling` | box, cylinder, group, transform, rename, erase, select |
137
+ | `sketchup-materials` | list, create, update, assign, remove |
138
+ | `sketchup-scenes` | list/create/update/remove scenes and Tags |
139
+
140
+ File paths must be absolute. Existing export and copy targets are refused unless
141
+ `overwrite=true`. Removing a material or Tag is refused while model content uses
142
+ it. The default Untagged Tag is never removable.
143
+
144
+ ## Development and verification
145
+
146
+ ```bash
147
+ python -m pip install -e ".[dev]"
148
+ python -m pytest
149
+ python -m ruff check src tests
150
+ python -m ruff format --check src tests
151
+ python -m build
152
+ python -m twine check dist/*
153
+ ruby tests/ruby/test_commands.rb
154
+ ```
155
+
156
+ CI covers Python 3.9 through 3.12 on Windows, macOS, and Linux, plus Ruby
157
+ syntax and command-contract tests. A production release additionally requires a
158
+ real SketchUp Desktop smoke test and a fresh installation from public PyPI.
159
+
160
+ ## Security boundary
161
+
162
+ - Loopback-only listener on an operating-system-assigned port.
163
+ - Random per-session bearer token with constant-time comparison.
164
+ - Correlated request IDs, deadlines, 1 MiB request/response limits, and a bounded queue.
165
+ - Fixed typed command allowlist; no `eval`, arbitrary Ruby, shell, or generic property access.
166
+ - Sidecar is bound to one SketchUp PID and stops when that host exits.
167
+ - Installer owns only `dcc_mcp_sketchup.rb` and the `dcc_mcp_sketchup/` directory.
168
+
169
+ ## References
170
+
171
+ - [SketchUp Ruby API](https://ruby.sketchup.com/)
172
+ - [SketchUp extension registration tutorial](https://developer.sketchup.com/tut-hello-cube-rb)
173
+ - [SketchUp model API](https://ruby.sketchup.com/Sketchup/Model.html)
174
+ - [SketchUp UI timer API](https://ruby.sketchup.com/UI)
175
+
176
+ ## License
177
+
178
+ MIT. SketchUp and its marks are property of Trimble Inc.; this project is an
179
+ independent integration and is not affiliated with or endorsed by Trimble.
@@ -0,0 +1,145 @@
1
+ # dcc-mcp-sketchup
2
+
3
+ <p align="center">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/dcc-mcp-sketchup-dark.svg">
6
+ <source media="(prefers-color-scheme: light)" srcset="docs/assets/dcc-mcp-sketchup.svg">
7
+ <img src="docs/assets/dcc-mcp-sketchup.svg" alt="DCC-MCP · SKETCHUP" width="600">
8
+ </picture>
9
+ </p>
10
+
11
+ Production SketchUp adapter for the [DCC-MCP](https://github.com/dcc-mcp) ecosystem.
12
+ It combines a small Ruby extension inside SketchUp with an external Python
13
+ sidecar, exposing 28 typed tools without arbitrary Ruby execution.
14
+
15
+ ![Typed SketchUp model inspection, construction, organization, validation, and interchange export](docs/images/sketchup-showcase.webp)
16
+
17
+ <sub>Workflow illustration generated with OpenAI image generation; no third-party source assets.</sub>
18
+
19
+ ## Capabilities
20
+
21
+ - Inspect model identity, bounds, entities, selection, and validation state.
22
+ - Save or copy `.skp` models and use installed SketchUp importers/exporters.
23
+ - Create boxes and cylinders, group, transform, rename, select, and erase entities.
24
+ - List, create, edit, assign, and safely remove materials.
25
+ - Manage saved scenes and Tags.
26
+ - Reference entities through SketchUp persistent IDs.
27
+ - Package four discoverable DCC-MCP Skills with complete JSON Schemas and MCP annotations.
28
+
29
+ ## Architecture
30
+
31
+ ```text
32
+ DCC-MCP client
33
+ |
34
+ v
35
+ external Python sidecar (dcc-mcp-core)
36
+ |
37
+ | authenticated, bounded JSON-RPC on 127.0.0.1
38
+ v
39
+ UI.start_timer callback on SketchUp's UI thread
40
+ |
41
+ | nonblocking, bounded socket pump + one-request queue
42
+ |
43
+ v
44
+ typed SketchUp Ruby API command map
45
+ ```
46
+
47
+ The Ruby extension has a single socket owner: the repeating UI timer. Each tick
48
+ uses zero-timeout `IO.select` and nonblocking accept, read, and write operations
49
+ across at most 16 connections, then executes at most one validated request.
50
+ Frame, response, connection, and deadline limits keep every tick bounded while
51
+ preserving SketchUp's thread affinity. No worker thread performs socket I/O or
52
+ calls the SketchUp API. Every model mutation is a named undoable operation.
53
+
54
+ ## Requirements
55
+
56
+ - SketchUp Desktop 2021 or newer on Windows or macOS.
57
+ - Python 3.9 or newer for the external sidecar.
58
+ - `dcc-mcp-core>=0.19.91,<1.0.0` (installed automatically).
59
+
60
+ Importer and exporter availability varies by SketchUp edition, version, and
61
+ installed extensions. The adapter reports the host error instead of claiming a
62
+ format is available when SketchUp rejects it.
63
+
64
+ ## Install
65
+
66
+ Install the package into the Python environment used by DCC-MCP:
67
+
68
+ ```bash
69
+ python -m pip install dcc-mcp-sketchup
70
+ ```
71
+
72
+ Start SketchUp once so its versioned user profile exists, then install the Ruby
73
+ extension:
74
+
75
+ ```bash
76
+ dcc-mcp-sketchup install
77
+ ```
78
+
79
+ For a specific SketchUp profile, pass its Plugins directory explicitly:
80
+
81
+ ```bash
82
+ dcc-mcp-sketchup install \
83
+ --plugins-dir "C:/Users/you/AppData/Roaming/SketchUp/SketchUp 2026/SketchUp/Plugins"
84
+ ```
85
+
86
+ Restart SketchUp. The extension binds an ephemeral loopback port, generates a
87
+ random token, and launches the host-bound sidecar automatically. It terminates
88
+ the sidecar when SketchUp exits.
89
+
90
+ To update or remove only files owned by this package:
91
+
92
+ ```bash
93
+ dcc-mcp-sketchup install --overwrite
94
+ dcc-mcp-sketchup uninstall
95
+ ```
96
+
97
+ ## Skills and tools
98
+
99
+ | Skill | Tools |
100
+ | --- | --- |
101
+ | `sketchup-session` | status, inspection, root entities, save, copy, validate, import, export |
102
+ | `sketchup-modeling` | box, cylinder, group, transform, rename, erase, select |
103
+ | `sketchup-materials` | list, create, update, assign, remove |
104
+ | `sketchup-scenes` | list/create/update/remove scenes and Tags |
105
+
106
+ File paths must be absolute. Existing export and copy targets are refused unless
107
+ `overwrite=true`. Removing a material or Tag is refused while model content uses
108
+ it. The default Untagged Tag is never removable.
109
+
110
+ ## Development and verification
111
+
112
+ ```bash
113
+ python -m pip install -e ".[dev]"
114
+ python -m pytest
115
+ python -m ruff check src tests
116
+ python -m ruff format --check src tests
117
+ python -m build
118
+ python -m twine check dist/*
119
+ ruby tests/ruby/test_commands.rb
120
+ ```
121
+
122
+ CI covers Python 3.9 through 3.12 on Windows, macOS, and Linux, plus Ruby
123
+ syntax and command-contract tests. A production release additionally requires a
124
+ real SketchUp Desktop smoke test and a fresh installation from public PyPI.
125
+
126
+ ## Security boundary
127
+
128
+ - Loopback-only listener on an operating-system-assigned port.
129
+ - Random per-session bearer token with constant-time comparison.
130
+ - Correlated request IDs, deadlines, 1 MiB request/response limits, and a bounded queue.
131
+ - Fixed typed command allowlist; no `eval`, arbitrary Ruby, shell, or generic property access.
132
+ - Sidecar is bound to one SketchUp PID and stops when that host exits.
133
+ - Installer owns only `dcc_mcp_sketchup.rb` and the `dcc_mcp_sketchup/` directory.
134
+
135
+ ## References
136
+
137
+ - [SketchUp Ruby API](https://ruby.sketchup.com/)
138
+ - [SketchUp extension registration tutorial](https://developer.sketchup.com/tut-hello-cube-rb)
139
+ - [SketchUp model API](https://ruby.sketchup.com/Sketchup/Model.html)
140
+ - [SketchUp UI timer API](https://ruby.sketchup.com/UI)
141
+
142
+ ## License
143
+
144
+ MIT. SketchUp and its marks are property of Trimble Inc.; this project is an
145
+ independent integration and is not affiliated with or endorsed by Trimble.
@@ -0,0 +1,54 @@
1
+ # Architecture and host contract
2
+
3
+ The adapter has two process boundaries and one mandatory thread-affinity hop.
4
+
5
+ 1. The Ruby extension loads on SketchUp's UI thread, binds an ephemeral
6
+ `127.0.0.1` TCP listener, generates a random token, and starts the installed
7
+ `dcc-mcp-sketchup` console script.
8
+ 2. The external Python sidecar hosts `dcc-mcp-core`, binds its lifecycle to the
9
+ SketchUp PID, and sends only allowlisted command names over authenticated
10
+ newline-delimited JSON-RPC.
11
+ 3. A repeating `UI.start_timer` callback is the only owner of accepted sockets.
12
+ It multiplexes at most 16 connections with zero-timeout `IO.select` and
13
+ nonblocking accept, read, and write operations. Complete one-line frames are
14
+ validated before entering a one-request queue.
15
+ 4. The same callback executes at most one typed command per tick on SketchUp's
16
+ UI thread, then pumps the correlated response without blocking. There is no
17
+ Ruby network worker thread.
18
+
19
+ ## Protocol invariants
20
+
21
+ - The listener is always loopback and uses an operating-system-assigned port.
22
+ - Every request carries JSON-RPC version `2.0`, a unique 32-character hex ID, a random
23
+ token, a typed method name, and an object-valued parameter map.
24
+ - Non-health requests include an absolute millisecond deadline.
25
+ - Requests, responses, connections, per-socket I/O, and queues are bounded.
26
+ - Read, request, write, and peer-close phases all have deadlines; a connection
27
+ carries exactly one newline-delimited request.
28
+ - Responses echo the request ID; the sidecar rejects mismatches.
29
+ - The extension launches one sidecar and terminates it on SketchUp shutdown.
30
+
31
+ ## Mutation invariants
32
+
33
+ - No command evaluates source or dispatches arbitrary methods.
34
+ - Every model mutation is wrapped by `start_operation`, `commit_operation`, and
35
+ `abort_operation` on failure.
36
+ - Persistent IDs are the public entity reference.
37
+ - Destructive operations require explicit targets and are marked with MCP
38
+ `destructive_hint` annotations.
39
+ - File output rejects an existing target unless `overwrite=true`.
40
+
41
+ ## Verification tiers
42
+
43
+ 1. Python unit tests cover protocol validation, installer ownership, sidecar PID
44
+ binding, Skill validation, and manifest-to-script integrity.
45
+ 2. Ruby tests cover syntax, the bounded command allowlist, deadlines, unit
46
+ conversion, persistent IDs, and undo transaction boundaries with host mocks.
47
+ 3. Real-host acceptance uses a supported SketchUp Desktop build to create,
48
+ inspect, validate, save, and export geometry through typed DCC-MCP tools;
49
+ the exported artifact is parsed independently of SketchUp.
50
+ 4. Cross-DCC acceptance additionally imports a supported export into another
51
+ live DCC when that host is available. It is reported separately and is never
52
+ inferred from a successful SketchUp export alone.
53
+ 5. Release acceptance installs the published wheel from public PyPI into a new
54
+ environment and repeats artifact/entry-point checks.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires=["hatchling>=1.26"]
3
+ build-backend="hatchling.build"
4
+
5
+ [project]
6
+ name="dcc-mcp-sketchup"
7
+ version="0.1.0"
8
+ description="Production SketchUp adapter with an authenticated Ruby main-thread bridge for DCC-MCP"
9
+ readme="README.md"
10
+ requires-python=">=3.9"
11
+ license={text="MIT"}
12
+ authors=[{name="loonghao",email="hal.long@outlook.com"}]
13
+ keywords=["sketchup","cad","3d-modeling","dcc","mcp","automation"]
14
+ classifiers=[
15
+ "Development Status :: 4 - Beta",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Operating System :: Microsoft :: Windows",
23
+ "Operating System :: MacOS",
24
+ ]
25
+ dependencies=["dcc-mcp-core>=0.19.91,<1.0.0"]
26
+
27
+ [project.urls]
28
+ Homepage="https://github.com/dcc-mcp/dcc-mcp-sketchup"
29
+ Repository="https://github.com/dcc-mcp/dcc-mcp-sketchup"
30
+ Issues="https://github.com/dcc-mcp/dcc-mcp-sketchup/issues"
31
+
32
+ [project.optional-dependencies]
33
+ test=["pytest>=8","ruff>=0.8","PyYAML>=6"]
34
+ dev=["pytest>=8","ruff>=0.8","PyYAML>=6","build>=1.2","twine>=6"]
35
+
36
+ [project.scripts]
37
+ dcc-mcp-sketchup="dcc_mcp_sketchup.server:main"
38
+
39
+ [project.entry-points."dcc_mcp.adapters"]
40
+ sketchup="dcc_mcp_sketchup:SketchupMcpServer"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages=["src/dcc_mcp_sketchup"]
44
+ artifacts=["src/dcc_mcp_sketchup/skills/**","src/dcc_mcp_sketchup/sketchup_plugin/**"]
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ exclude=["docs/**/*.png", "docs/**/*.svg", "docs/**/*.webp"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths=["tests"]
51
+ pythonpath=["src","."]
52
+
53
+ [tool.ruff.lint]
54
+ select=["E","F","I","B"]
55
+
56
+ [tool.ruff]
57
+ line-length=100
58
+ target-version="py39"
@@ -0,0 +1,19 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "python",
5
+ "package-name": "dcc-mcp-sketchup",
6
+ "changelog-path": "CHANGELOG.md",
7
+ "include-component-in-tag": false,
8
+ "extra-files": [
9
+ {"type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version"},
10
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/__version__.py"},
11
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/sketchup_plugin/registration.rb"},
12
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/skills/sketchup-session/SKILL.md"},
13
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/skills/sketchup-modeling/SKILL.md"},
14
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/skills/sketchup-materials/SKILL.md"},
15
+ {"type": "generic", "path": "src/dcc_mcp_sketchup/skills/sketchup-scenes/SKILL.md"}
16
+ ]
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ from .__version__ import __version__
2
+ from .server import SketchupMcpServer
3
+
4
+ __all__ = ["SketchupMcpServer", "__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0" # x-release-please-version