synfig-mcp 1.0.3__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.
- synfig_mcp-1.0.3/.gitignore +40 -0
- synfig_mcp-1.0.3/PKG-INFO +137 -0
- synfig_mcp-1.0.3/README.md +90 -0
- synfig_mcp-1.0.3/pyproject.toml +135 -0
- synfig_mcp-1.0.3/src/synfig_mcp/__init__.py +5 -0
- synfig_mcp-1.0.3/src/synfig_mcp/__main__.py +8 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/__init__.py +8 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/models.py +129 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/pipeline.py +226 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/base.py +37 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/mock_provider.py +103 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/openverse.py +108 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/security/raster_hardening.py +71 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/security/svg_sanitizer.py +152 -0
- synfig_mcp-1.0.3/src/synfig_mcp/assets/security/url_validator.py +82 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/auto.py +589 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/base.py +543 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/detect.py +63 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/headless.py +1401 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/live.py +1056 -0
- synfig_mcp-1.0.3/src/synfig_mcp/backend/select.py +58 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/__init__.py +25 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/cutout.py +1038 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/models.py +107 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/production.py +451 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/rigging.py +423 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/sheet.py +403 -0
- synfig_mcp-1.0.3/src/synfig_mcp/characters/storyboard.py +102 -0
- synfig_mcp-1.0.3/src/synfig_mcp/cli/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/cli/runner.py +432 -0
- synfig_mcp-1.0.3/src/synfig_mcp/config.py +130 -0
- synfig_mcp-1.0.3/src/synfig_mcp/doctor.py +415 -0
- synfig_mcp-1.0.3/src/synfig_mcp/errors.py +126 -0
- synfig_mcp-1.0.3/src/synfig_mcp/http_transport.py +141 -0
- synfig_mcp-1.0.3/src/synfig_mcp/logging.py +42 -0
- synfig_mcp-1.0.3/src/synfig_mcp/prompts/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/protocol/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/protocol/client.py +272 -0
- synfig_mcp-1.0.3/src/synfig_mcp/protocol/fake_bridge.py +1146 -0
- synfig_mcp-1.0.3/src/synfig_mcp/protocol/models.py +212 -0
- synfig_mcp-1.0.3/src/synfig_mcp/recipes/__init__.py +19 -0
- synfig_mcp-1.0.3/src/synfig_mcp/recipes/engine.py +250 -0
- synfig_mcp-1.0.3/src/synfig_mcp/recipes/models.py +68 -0
- synfig_mcp-1.0.3/src/synfig_mcp/recipes/registry.py +370 -0
- synfig_mcp-1.0.3/src/synfig_mcp/recipes/styles.py +85 -0
- synfig_mcp-1.0.3/src/synfig_mcp/render/__init__.py +8 -0
- synfig_mcp-1.0.3/src/synfig_mcp/render/cache.py +52 -0
- synfig_mcp-1.0.3/src/synfig_mcp/render/jobs.py +166 -0
- synfig_mcp-1.0.3/src/synfig_mcp/render/pipeline.py +335 -0
- synfig_mcp-1.0.3/src/synfig_mcp/render/targets.py +128 -0
- synfig_mcp-1.0.3/src/synfig_mcp/resources/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/resources/guides.py +180 -0
- synfig_mcp-1.0.3/src/synfig_mcp/review/__init__.py +7 -0
- synfig_mcp-1.0.3/src/synfig_mcp/review/metrics.py +287 -0
- synfig_mcp-1.0.3/src/synfig_mcp/review/rubrics.py +85 -0
- synfig_mcp-1.0.3/src/synfig_mcp/review/service.py +227 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/__init__.py +20 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/cache.py +37 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/param_types.json +566 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/param_types.py +71 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/probe.py +322 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/registry.py +159 -0
- synfig_mcp-1.0.3/src/synfig_mcp/schema/scene_spec.py +61 -0
- synfig_mcp-1.0.3/src/synfig_mcp/security/__init__.py +1 -0
- synfig_mcp-1.0.3/src/synfig_mcp/security/sandbox.py +173 -0
- synfig_mcp-1.0.3/src/synfig_mcp/server.py +133 -0
- synfig_mcp-1.0.3/src/synfig_mcp/sif/__init__.py +23 -0
- synfig_mcp-1.0.3/src/synfig_mcp/sif/numerics.py +105 -0
- synfig_mcp-1.0.3/src/synfig_mcp/sif/parser.py +2213 -0
- synfig_mcp-1.0.3/src/synfig_mcp/sif/valuenodes.py +621 -0
- synfig_mcp-1.0.3/src/synfig_mcp/sif/writer.py +44 -0
- synfig_mcp-1.0.3/src/synfig_mcp/tools/__init__.py +5 -0
- synfig_mcp-1.0.3/src/synfig_mcp/tools/register.py +1704 -0
- synfig_mcp-1.0.3/src/synfig_mcp/validation.py +177 -0
- synfig_mcp-1.0.3/tests/__init__.py +1 -0
- synfig_mcp-1.0.3/tests/conftest.py +59 -0
- synfig_mcp-1.0.3/tests/contract/test_asset_pipeline.py +129 -0
- synfig_mcp-1.0.3/tests/contract/test_backend_parity.py +317 -0
- synfig_mcp-1.0.3/tests/contract/test_character_rig.py +227 -0
- synfig_mcp-1.0.3/tests/contract/test_client_fake_bridge.py +129 -0
- synfig_mcp-1.0.3/tests/contract/test_edge_cases.py +100 -0
- synfig_mcp-1.0.3/tests/contract/test_handshake.py +63 -0
- synfig_mcp-1.0.3/tests/contract/test_mcp_tools.py +737 -0
- synfig_mcp-1.0.3/tests/contract/test_recipe_apply.py +143 -0
- synfig_mcp-1.0.3/tests/contract/test_render_targets.py +117 -0
- synfig_mcp-1.0.3/tests/contract/test_review_tools.py +169 -0
- synfig_mcp-1.0.3/tests/contract/test_scene_auto_build.py +202 -0
- synfig_mcp-1.0.3/tests/contract/test_scene_build.py +213 -0
- synfig_mcp-1.0.3/tests/contract/test_schema_fixtures.py +61 -0
- synfig_mcp-1.0.3/tests/contract/test_sif_synfig_validity.py +734 -0
- synfig_mcp-1.0.3/tests/contract/test_tool_errors.py +28 -0
- synfig_mcp-1.0.3/tests/e2e/__init__.py +1 -0
- synfig_mcp-1.0.3/tests/e2e/conftest.py +160 -0
- synfig_mcp-1.0.3/tests/e2e/test_live_mutate.py +1874 -0
- synfig_mcp-1.0.3/tests/e2e/test_live_read.py +162 -0
- synfig_mcp-1.0.3/tests/e2e/test_live_scene_build.py +448 -0
- synfig_mcp-1.0.3/tests/e2e/test_shipped_release.py +161 -0
- synfig_mcp-1.0.3/tests/fixtures/sample_project.sif +57 -0
- synfig_mcp-1.0.3/tests/property/test_sif_lossless.py +147 -0
- synfig_mcp-1.0.3/tests/support/__init__.py +1 -0
- synfig_mcp-1.0.3/tests/support/rig_assets.py +71 -0
- synfig_mcp-1.0.3/tests/unit/test_assets_security.py +104 -0
- synfig_mcp-1.0.3/tests/unit/test_backend.py +42 -0
- synfig_mcp-1.0.3/tests/unit/test_backend_auto.py +144 -0
- synfig_mcp-1.0.3/tests/unit/test_backend_select.py +91 -0
- synfig_mcp-1.0.3/tests/unit/test_characters.py +94 -0
- synfig_mcp-1.0.3/tests/unit/test_cli_runner.py +57 -0
- synfig_mcp-1.0.3/tests/unit/test_config.py +55 -0
- synfig_mcp-1.0.3/tests/unit/test_coverage_matrix.py +145 -0
- synfig_mcp-1.0.3/tests/unit/test_cutout_rig.py +263 -0
- synfig_mcp-1.0.3/tests/unit/test_doctor.py +108 -0
- synfig_mcp-1.0.3/tests/unit/test_errors.py +25 -0
- synfig_mcp-1.0.3/tests/unit/test_headless_backend.py +292 -0
- synfig_mcp-1.0.3/tests/unit/test_http_transport.py +95 -0
- synfig_mcp-1.0.3/tests/unit/test_protocol_docs.py +102 -0
- synfig_mcp-1.0.3/tests/unit/test_protocol_models.py +119 -0
- synfig_mcp-1.0.3/tests/unit/test_recipes.py +80 -0
- synfig_mcp-1.0.3/tests/unit/test_render_pipeline.py +227 -0
- synfig_mcp-1.0.3/tests/unit/test_review_metrics.py +164 -0
- synfig_mcp-1.0.3/tests/unit/test_review_service.py +22 -0
- synfig_mcp-1.0.3/tests/unit/test_rig_sheet.py +105 -0
- synfig_mcp-1.0.3/tests/unit/test_sandbox.py +168 -0
- synfig_mcp-1.0.3/tests/unit/test_schema_registry.py +211 -0
- synfig_mcp-1.0.3/tests/unit/test_sif.py +15 -0
- synfig_mcp-1.0.3/tests/unit/test_sif_values.py +369 -0
- synfig_mcp-1.0.3/tests/unit/test_token_budget.py +109 -0
- synfig_mcp-1.0.3/tests/unit/test_tool_docs.py +72 -0
- synfig_mcp-1.0.3/uv.lock +2019 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Git
|
|
2
|
+
.git/
|
|
3
|
+
|
|
4
|
+
# Build directories
|
|
5
|
+
build_synfig/
|
|
6
|
+
synfig_upstream/
|
|
7
|
+
cmake-build/
|
|
8
|
+
*.o
|
|
9
|
+
*.obj
|
|
10
|
+
*.exe
|
|
11
|
+
*.dll
|
|
12
|
+
*.a
|
|
13
|
+
|
|
14
|
+
# Python
|
|
15
|
+
__pycache__/
|
|
16
|
+
*.py[cod]
|
|
17
|
+
*$py.class
|
|
18
|
+
.venv/
|
|
19
|
+
venv/
|
|
20
|
+
dist/
|
|
21
|
+
build/
|
|
22
|
+
site/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.vscode/
|
|
32
|
+
.idea/
|
|
33
|
+
|
|
34
|
+
# Operating system
|
|
35
|
+
Thumbs.db
|
|
36
|
+
[Dd]esktop.ini
|
|
37
|
+
$RECYCLE.BIN/
|
|
38
|
+
*.log
|
|
39
|
+
scratch/
|
|
40
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: synfig-mcp
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Live MCP control of Synfig Studio (native bridge + headless fallback)
|
|
5
|
+
Project-URL: Homepage, https://github.com/anusara-bemal/synfig-mcp
|
|
6
|
+
Project-URL: Documentation, https://github.com/anusara-bemal/synfig-mcp/tree/v1.0.0/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/anusara-bemal/synfig-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/anusara-bemal/synfig-mcp/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/CHANGELOG.md
|
|
10
|
+
Author: Synfig MCP Contributors
|
|
11
|
+
Author-email: ABM <anusarabemal805@gmail.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
Keywords: ai,animation,llm,mcp,synfig
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: anyio>=4.3.0
|
|
25
|
+
Requires-Dist: lxml>=5.2.0
|
|
26
|
+
Requires-Dist: mcp>=1.2.0
|
|
27
|
+
Requires-Dist: numpy>=1.26.0
|
|
28
|
+
Requires-Dist: pillow>=10.3.0
|
|
29
|
+
Requires-Dist: pydantic>=2.7.0
|
|
30
|
+
Requires-Dist: rich>=13.7.0
|
|
31
|
+
Requires-Dist: structlog>=24.1.0
|
|
32
|
+
Requires-Dist: typer>=0.12.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: bandit>=1.7.8; extra == 'dev'
|
|
35
|
+
Requires-Dist: hypothesis>=6.100.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: poethepoet>=0.25.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.2.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: syrupy>=4.6.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: tiktoken>=0.7.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: types-jsonschema>=4.21.0.20240118; extra == 'dev'
|
|
44
|
+
Requires-Dist: types-lxml>=2024.4.14; extra == 'dev'
|
|
45
|
+
Requires-Dist: types-pillow>=10.2.0.20240520; extra == 'dev'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# synfig-mcp
|
|
49
|
+
|
|
50
|
+
[](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml)
|
|
51
|
+
[](https://pypi.org/project/synfig-mcp/)
|
|
52
|
+
[](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/LICENSE)
|
|
53
|
+
|
|
54
|
+
**Model Context Protocol server for Synfig Studio — live canvas automation with a headless fallback.**
|
|
55
|
+
|
|
56
|
+
This package provides the Python MCP server. It can drive a running patched Synfig Studio over an authenticated local bridge, or operate entirely headless using the `synfig` CLI.
|
|
57
|
+
|
|
58
|
+
Full documentation, screenshots, client setup, and prebuilt Studio bundles: **[github.com/anusara-bemal/synfig-mcp](https://github.com/anusara-bemal/synfig-mcp)**.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv pip install "synfig-mcp @ https://github.com/anusara-bemal/synfig-mcp/releases/download/v0.8.0-rc2/synfig_mcp-0.8.0rc2-py3-none-any.whl"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or from source:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv pip install "git+https://github.com/anusara-bemal/synfig-mcp.git#subdirectory=server"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Use
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
synfig-mcp doctor
|
|
76
|
+
synfig-mcp
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Example MCP client configuration:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"synfig": {
|
|
85
|
+
"command": "synfig-mcp",
|
|
86
|
+
"env": {
|
|
87
|
+
"SYNFIG_MCP_PROFILE": "core",
|
|
88
|
+
"SYNFIG_MCP_BACKEND": "auto"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Operating modes
|
|
96
|
+
|
|
97
|
+
| Mode | Backend | Use case |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `auto` | Live if reachable, otherwise headless | Desktop |
|
|
100
|
+
| `live` | Patched Synfig Studio | Interactive editing with undo/redo |
|
|
101
|
+
| `headless` | SIF engine + `synfig` CLI | Servers, CI, containers |
|
|
102
|
+
|
|
103
|
+
## Tool profiles
|
|
104
|
+
|
|
105
|
+
`SYNFIG_MCP_PROFILE` selects the exposed tools:
|
|
106
|
+
|
|
107
|
+
| Profile | Tools | Approx. tokens |
|
|
108
|
+
|---|---:|---:|
|
|
109
|
+
| `core` (default) | 12 | ~2,658 |
|
|
110
|
+
| `anim` | 14 | ~3,800 |
|
|
111
|
+
| `fx` | 21 | ~4,200 |
|
|
112
|
+
| `rig` | 12 | ~2,800 |
|
|
113
|
+
| `assets` | 10 | ~1,900 |
|
|
114
|
+
| `review` | 15 | ~3,900 |
|
|
115
|
+
| `all` | 56 | ~18,500 |
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
| Variable | Default | Description |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `SYNFIG_MCP_PROFILE` | `core` | Tool profile |
|
|
122
|
+
| `SYNFIG_MCP_BACKEND` | `auto` | `auto`, `live`, or `headless` |
|
|
123
|
+
| `SYNFIG_MCP_HOST` / `SYNFIG_MCP_PORT` | `127.0.0.1` / `19998` | Live bridge endpoint |
|
|
124
|
+
| `SYNFIG_MCP_TOKEN_PATH` | `~/.synfig/mcp_token` | Bridge token |
|
|
125
|
+
| `SYNFIG_MCP_WORKSPACE_ROOTS` | `~/Documents/SynfigMCP` | Sandbox roots |
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd server
|
|
131
|
+
uv sync --locked --all-extras
|
|
132
|
+
uv run poe check
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
The Python server is MIT licensed. The native bridge and patched Studio binaries are GPL-3.0-or-later. See [LICENSE](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/LICENSE) and [docs/gpl-compliance.md](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/docs/gpl-compliance.md).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# synfig-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml)
|
|
4
|
+
[](https://pypi.org/project/synfig-mcp/)
|
|
5
|
+
[](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/LICENSE)
|
|
6
|
+
|
|
7
|
+
**Model Context Protocol server for Synfig Studio — live canvas automation with a headless fallback.**
|
|
8
|
+
|
|
9
|
+
This package provides the Python MCP server. It can drive a running patched Synfig Studio over an authenticated local bridge, or operate entirely headless using the `synfig` CLI.
|
|
10
|
+
|
|
11
|
+
Full documentation, screenshots, client setup, and prebuilt Studio bundles: **[github.com/anusara-bemal/synfig-mcp](https://github.com/anusara-bemal/synfig-mcp)**.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv pip install "synfig-mcp @ https://github.com/anusara-bemal/synfig-mcp/releases/download/v0.8.0-rc2/synfig_mcp-0.8.0rc2-py3-none-any.whl"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or from source:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv pip install "git+https://github.com/anusara-bemal/synfig-mcp.git#subdirectory=server"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Use
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
synfig-mcp doctor
|
|
29
|
+
synfig-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Example MCP client configuration:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"synfig": {
|
|
38
|
+
"command": "synfig-mcp",
|
|
39
|
+
"env": {
|
|
40
|
+
"SYNFIG_MCP_PROFILE": "core",
|
|
41
|
+
"SYNFIG_MCP_BACKEND": "auto"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Operating modes
|
|
49
|
+
|
|
50
|
+
| Mode | Backend | Use case |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `auto` | Live if reachable, otherwise headless | Desktop |
|
|
53
|
+
| `live` | Patched Synfig Studio | Interactive editing with undo/redo |
|
|
54
|
+
| `headless` | SIF engine + `synfig` CLI | Servers, CI, containers |
|
|
55
|
+
|
|
56
|
+
## Tool profiles
|
|
57
|
+
|
|
58
|
+
`SYNFIG_MCP_PROFILE` selects the exposed tools:
|
|
59
|
+
|
|
60
|
+
| Profile | Tools | Approx. tokens |
|
|
61
|
+
|---|---:|---:|
|
|
62
|
+
| `core` (default) | 12 | ~2,658 |
|
|
63
|
+
| `anim` | 14 | ~3,800 |
|
|
64
|
+
| `fx` | 21 | ~4,200 |
|
|
65
|
+
| `rig` | 12 | ~2,800 |
|
|
66
|
+
| `assets` | 10 | ~1,900 |
|
|
67
|
+
| `review` | 15 | ~3,900 |
|
|
68
|
+
| `all` | 56 | ~18,500 |
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
| Variable | Default | Description |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `SYNFIG_MCP_PROFILE` | `core` | Tool profile |
|
|
75
|
+
| `SYNFIG_MCP_BACKEND` | `auto` | `auto`, `live`, or `headless` |
|
|
76
|
+
| `SYNFIG_MCP_HOST` / `SYNFIG_MCP_PORT` | `127.0.0.1` / `19998` | Live bridge endpoint |
|
|
77
|
+
| `SYNFIG_MCP_TOKEN_PATH` | `~/.synfig/mcp_token` | Bridge token |
|
|
78
|
+
| `SYNFIG_MCP_WORKSPACE_ROOTS` | `~/Documents/SynfigMCP` | Sandbox roots |
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cd server
|
|
84
|
+
uv sync --locked --all-extras
|
|
85
|
+
uv run poe check
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
The Python server is MIT licensed. The native bridge and patched Studio binaries are GPL-3.0-or-later. See [LICENSE](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/LICENSE) and [docs/gpl-compliance.md](https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/docs/gpl-compliance.md).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "synfig-mcp"
|
|
7
|
+
version = "1.0.3"
|
|
8
|
+
description = "Live MCP control of Synfig Studio (native bridge + headless fallback)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "ABM", email = "anusarabemal805@gmail.com" },
|
|
14
|
+
{ name = "Synfig MCP Contributors" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["mcp", "synfig", "animation", "llm", "ai"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Multimedia :: Graphics",
|
|
25
|
+
"Topic :: Multimedia :: Video",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"mcp>=1.2.0",
|
|
31
|
+
"pydantic>=2.7.0",
|
|
32
|
+
"lxml>=5.2.0",
|
|
33
|
+
"anyio>=4.3.0",
|
|
34
|
+
"pillow>=10.3.0",
|
|
35
|
+
"numpy>=1.26.0",
|
|
36
|
+
"typer>=0.12.0",
|
|
37
|
+
"rich>=13.7.0",
|
|
38
|
+
"structlog>=24.1.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/anusara-bemal/synfig-mcp"
|
|
43
|
+
Documentation = "https://github.com/anusara-bemal/synfig-mcp/tree/v1.0.0/docs"
|
|
44
|
+
Repository = "https://github.com/anusara-bemal/synfig-mcp"
|
|
45
|
+
Issues = "https://github.com/anusara-bemal/synfig-mcp/issues"
|
|
46
|
+
Changelog = "https://github.com/anusara-bemal/synfig-mcp/blob/v1.0.0/CHANGELOG.md"
|
|
47
|
+
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
dev = [
|
|
50
|
+
"poethepoet>=0.25.0",
|
|
51
|
+
"ruff>=0.4.0",
|
|
52
|
+
"mypy>=1.10.0",
|
|
53
|
+
"pytest>=8.2.0",
|
|
54
|
+
"pytest-asyncio>=0.23.0",
|
|
55
|
+
"hypothesis>=6.100.0",
|
|
56
|
+
"syrupy>=4.6.0",
|
|
57
|
+
"bandit>=1.7.8",
|
|
58
|
+
"types-jsonschema>=4.21.0.20240118",
|
|
59
|
+
"types-lxml>=2024.4.14",
|
|
60
|
+
"types-Pillow>=10.2.0.20240520",
|
|
61
|
+
"tiktoken>=0.7.0",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[project.scripts]
|
|
65
|
+
synfig-mcp = "synfig_mcp.server:main"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["src/synfig_mcp"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff]
|
|
71
|
+
line-length = 100
|
|
72
|
+
target-version = "py312"
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = [
|
|
76
|
+
"E", # pycodestyle errors
|
|
77
|
+
"W", # pycodestyle warnings
|
|
78
|
+
"F", # pyflakes
|
|
79
|
+
"I", # isort
|
|
80
|
+
"B", # flake8-bugbear
|
|
81
|
+
"C4", # flake8-comprehensions
|
|
82
|
+
"UP", # pyupgrade
|
|
83
|
+
"ARG", # flake8-unused-arguments
|
|
84
|
+
"SIM", # flake8-simplify
|
|
85
|
+
]
|
|
86
|
+
ignore = [
|
|
87
|
+
"E501", # line too long (handled by formatter)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.ruff.format]
|
|
91
|
+
quote-style = "double"
|
|
92
|
+
indent-style = "space"
|
|
93
|
+
|
|
94
|
+
[tool.mypy]
|
|
95
|
+
python_version = "3.12"
|
|
96
|
+
strict = true
|
|
97
|
+
warn_return_any = true
|
|
98
|
+
warn_unused_configs = true
|
|
99
|
+
disallow_untyped_defs = true
|
|
100
|
+
disallow_incomplete_defs = true
|
|
101
|
+
check_untyped_defs = true
|
|
102
|
+
no_implicit_optional = true
|
|
103
|
+
warn_redundant_casts = true
|
|
104
|
+
warn_unused_ignores = true
|
|
105
|
+
|
|
106
|
+
[[tool.mypy.overrides]]
|
|
107
|
+
module = [
|
|
108
|
+
"lxml.*",
|
|
109
|
+
"jsonschema.*",
|
|
110
|
+
]
|
|
111
|
+
ignore_missing_imports = true
|
|
112
|
+
|
|
113
|
+
[tool.pytest.ini_options]
|
|
114
|
+
minversion = "8.0"
|
|
115
|
+
testpaths = ["tests"]
|
|
116
|
+
asyncio_mode = "auto"
|
|
117
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
118
|
+
markers = [
|
|
119
|
+
"real_studio: tests requiring real patched Synfig Studio binary",
|
|
120
|
+
]
|
|
121
|
+
filterwarnings = [
|
|
122
|
+
"ignore::DeprecationWarning",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
[tool.poe.tasks]
|
|
126
|
+
format = "ruff format src tests"
|
|
127
|
+
format-check = "ruff format --check src tests"
|
|
128
|
+
lint = "ruff check src tests"
|
|
129
|
+
lint-fix = "ruff check --fix src tests"
|
|
130
|
+
typecheck = "mypy src tests"
|
|
131
|
+
test = "pytest -m 'not real_studio' -rs"
|
|
132
|
+
live = "pytest -m real_studio -v -s"
|
|
133
|
+
check = ["format-check", "lint", "typecheck", "test"]
|
|
134
|
+
security = "bandit -r src/"
|
|
135
|
+
verify-validity = "pytest tests/contract/test_sif_synfig_validity.py -v"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Licensed Asset Pipeline (Phase A5).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from synfig_mcp.assets.models import AssetManifest, AssetMetadata, LicensePolicy
|
|
6
|
+
from synfig_mcp.assets.pipeline import AssetPipeline
|
|
7
|
+
|
|
8
|
+
__all__ = ["AssetManifest", "AssetMetadata", "AssetPipeline", "LicensePolicy"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for the Licensed Asset Pipeline, Provenance Manifest, and License Policies (Phase A5).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LicensePolicy(BaseModel):
|
|
13
|
+
"""
|
|
14
|
+
Configurable license policy for external assets.
|
|
15
|
+
Default configuration is strict: allows CC0/PDM, blocks ND/NC, flags BY/BY-SA.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
allow_cc0: bool = Field(default=True, description="Allow CC0 / Public Domain Dedication.")
|
|
19
|
+
allow_pdm: bool = Field(default=True, description="Allow Public Domain Mark.")
|
|
20
|
+
allow_by: bool = Field(default=False, description="Allow CC BY (attribution required).")
|
|
21
|
+
allow_by_sa: bool = Field(default=False, description="Allow CC BY-SA (Share-Alike required).")
|
|
22
|
+
allow_commercial: bool = Field(
|
|
23
|
+
default=True, description="Whether project requires commercial use."
|
|
24
|
+
)
|
|
25
|
+
block_nd: bool = Field(default=True, description="Block NoDerivatives (we transform assets).")
|
|
26
|
+
block_nc: bool = Field(default=True, description="Block NonCommercial if commercial.")
|
|
27
|
+
skip_mature: bool = Field(default=True, description="Reject mature/NSFW flagged content.")
|
|
28
|
+
|
|
29
|
+
def evaluate_license(self, license_code: str, is_mature: bool = False) -> tuple[bool, str]:
|
|
30
|
+
"""
|
|
31
|
+
Evaluate a license code against policy.
|
|
32
|
+
Returns: (allowed: bool, reason: str).
|
|
33
|
+
"""
|
|
34
|
+
if self.skip_mature and is_mature:
|
|
35
|
+
return False, "Asset is flagged as mature content."
|
|
36
|
+
|
|
37
|
+
code = license_code.lower().strip()
|
|
38
|
+
|
|
39
|
+
if code in ("cc0", "pdm", "public-domain"):
|
|
40
|
+
return True, "Permitted by policy (Public Domain / CC0)."
|
|
41
|
+
|
|
42
|
+
if "nd" in code and self.block_nd:
|
|
43
|
+
return (
|
|
44
|
+
False,
|
|
45
|
+
f"License '{code}' contains No-Derivatives (ND) clause; animation transformations prohibited.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if "nc" in code and self.block_nc and self.allow_commercial:
|
|
49
|
+
return (
|
|
50
|
+
False,
|
|
51
|
+
f"License '{code}' contains Non-Commercial (NC) restriction on a commercial project.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if code == "by":
|
|
55
|
+
if self.allow_by:
|
|
56
|
+
return True, "Permitted by policy with attribution."
|
|
57
|
+
return False, "License 'by' requires attribution and is disabled under strict policy."
|
|
58
|
+
|
|
59
|
+
if code in ("by-sa", "sa"):
|
|
60
|
+
if self.allow_by_sa:
|
|
61
|
+
return True, "Permitted by policy with Share-Alike obligation."
|
|
62
|
+
return (
|
|
63
|
+
False,
|
|
64
|
+
"License 'by-sa' requires Share-Alike and is disabled under strict policy.",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return False, f"License '{code}' is not permitted under active policy."
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AssetMetadata(BaseModel):
|
|
71
|
+
"""Provenance and licensing metadata captured at download time."""
|
|
72
|
+
|
|
73
|
+
id: str = Field(description="Unique asset identifier (e.g. 'openverse_12345').")
|
|
74
|
+
provider: str = Field(description="Source provider ('openverse', 'wikimedia', 'local').")
|
|
75
|
+
title: str = Field(description="Asset title or filename.")
|
|
76
|
+
creator: str = Field(default="Unknown", description="Original creator or copyright holder.")
|
|
77
|
+
license_code: str = Field(description="Standard license identifier ('cc0', 'pdm', 'by', etc.).")
|
|
78
|
+
license_url: str = Field(default="", description="Authoritative URL for license terms.")
|
|
79
|
+
attribution_text: str = Field(default="", description="Ready-to-use attribution snippet.")
|
|
80
|
+
download_url: str = Field(default="", description="Original HTTPS source download URL.")
|
|
81
|
+
thumbnail_url: str | None = Field(default=None, description="Small preview URL.")
|
|
82
|
+
filesize_bytes: int = Field(default=0, description="Size on disk in bytes.")
|
|
83
|
+
sha256: str = Field(default="", description="Cryptographic SHA-256 hash.")
|
|
84
|
+
local_path: str = Field(default="", description="Local file path within workspace.")
|
|
85
|
+
retrieved_at: str = Field(
|
|
86
|
+
default_factory=lambda: datetime.datetime.now(datetime.UTC).isoformat(),
|
|
87
|
+
description="ISO 8601 UTC timestamp of download.",
|
|
88
|
+
)
|
|
89
|
+
rights_declaration: str = Field(
|
|
90
|
+
default="", description="Declared rights basis ('own', 'licensed', 'cc0', 'public-domain')."
|
|
91
|
+
)
|
|
92
|
+
mature: bool = Field(default=False, description="Whether asset is flagged as mature.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AssetManifest(BaseModel):
|
|
96
|
+
"""Collection manifest of all imported assets stored at assets/manifest.json."""
|
|
97
|
+
|
|
98
|
+
version: str = Field(default="1.0")
|
|
99
|
+
assets: list[AssetMetadata] = Field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
def generate_attribution_md(self) -> str:
|
|
102
|
+
"""Auto-generate ATTRIBUTION.md for legal compliance and project distribution."""
|
|
103
|
+
lines = [
|
|
104
|
+
"# Project Asset Attribution & License Provenance",
|
|
105
|
+
"",
|
|
106
|
+
"> Auto-generated by Synfig MCP Asset Pipeline.",
|
|
107
|
+
"> Every external asset used in this animation project is recorded with source and terms snapshot.",
|
|
108
|
+
"",
|
|
109
|
+
"| Asset Title | Creator | License | Provider | SHA-256 | Local File |",
|
|
110
|
+
"|---|---|---|---|---|---|",
|
|
111
|
+
]
|
|
112
|
+
for a in self.assets:
|
|
113
|
+
c_link = f"[{a.creator}]({a.license_url})" if a.license_url else a.creator
|
|
114
|
+
lines.append(
|
|
115
|
+
f"| **{a.title}** | {c_link} | `{a.license_code.upper()}` | {a.provider} | `{a.sha256[:12]}...` | `{a.local_path}` |"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
lines.extend(
|
|
119
|
+
[
|
|
120
|
+
"",
|
|
121
|
+
"## Attribution Statements",
|
|
122
|
+
"",
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
for a in self.assets:
|
|
126
|
+
if a.attribution_text:
|
|
127
|
+
lines.append(f"- **{a.title}**: {a.attribution_text}")
|
|
128
|
+
|
|
129
|
+
return "\n".join(lines) + "\n"
|