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.
Files changed (129) hide show
  1. synfig_mcp-1.0.3/.gitignore +40 -0
  2. synfig_mcp-1.0.3/PKG-INFO +137 -0
  3. synfig_mcp-1.0.3/README.md +90 -0
  4. synfig_mcp-1.0.3/pyproject.toml +135 -0
  5. synfig_mcp-1.0.3/src/synfig_mcp/__init__.py +5 -0
  6. synfig_mcp-1.0.3/src/synfig_mcp/__main__.py +8 -0
  7. synfig_mcp-1.0.3/src/synfig_mcp/assets/__init__.py +8 -0
  8. synfig_mcp-1.0.3/src/synfig_mcp/assets/models.py +129 -0
  9. synfig_mcp-1.0.3/src/synfig_mcp/assets/pipeline.py +226 -0
  10. synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/base.py +37 -0
  11. synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/mock_provider.py +103 -0
  12. synfig_mcp-1.0.3/src/synfig_mcp/assets/providers/openverse.py +108 -0
  13. synfig_mcp-1.0.3/src/synfig_mcp/assets/security/raster_hardening.py +71 -0
  14. synfig_mcp-1.0.3/src/synfig_mcp/assets/security/svg_sanitizer.py +152 -0
  15. synfig_mcp-1.0.3/src/synfig_mcp/assets/security/url_validator.py +82 -0
  16. synfig_mcp-1.0.3/src/synfig_mcp/backend/__init__.py +1 -0
  17. synfig_mcp-1.0.3/src/synfig_mcp/backend/auto.py +589 -0
  18. synfig_mcp-1.0.3/src/synfig_mcp/backend/base.py +543 -0
  19. synfig_mcp-1.0.3/src/synfig_mcp/backend/detect.py +63 -0
  20. synfig_mcp-1.0.3/src/synfig_mcp/backend/headless.py +1401 -0
  21. synfig_mcp-1.0.3/src/synfig_mcp/backend/live.py +1056 -0
  22. synfig_mcp-1.0.3/src/synfig_mcp/backend/select.py +58 -0
  23. synfig_mcp-1.0.3/src/synfig_mcp/characters/__init__.py +25 -0
  24. synfig_mcp-1.0.3/src/synfig_mcp/characters/cutout.py +1038 -0
  25. synfig_mcp-1.0.3/src/synfig_mcp/characters/models.py +107 -0
  26. synfig_mcp-1.0.3/src/synfig_mcp/characters/production.py +451 -0
  27. synfig_mcp-1.0.3/src/synfig_mcp/characters/rigging.py +423 -0
  28. synfig_mcp-1.0.3/src/synfig_mcp/characters/sheet.py +403 -0
  29. synfig_mcp-1.0.3/src/synfig_mcp/characters/storyboard.py +102 -0
  30. synfig_mcp-1.0.3/src/synfig_mcp/cli/__init__.py +1 -0
  31. synfig_mcp-1.0.3/src/synfig_mcp/cli/runner.py +432 -0
  32. synfig_mcp-1.0.3/src/synfig_mcp/config.py +130 -0
  33. synfig_mcp-1.0.3/src/synfig_mcp/doctor.py +415 -0
  34. synfig_mcp-1.0.3/src/synfig_mcp/errors.py +126 -0
  35. synfig_mcp-1.0.3/src/synfig_mcp/http_transport.py +141 -0
  36. synfig_mcp-1.0.3/src/synfig_mcp/logging.py +42 -0
  37. synfig_mcp-1.0.3/src/synfig_mcp/prompts/__init__.py +1 -0
  38. synfig_mcp-1.0.3/src/synfig_mcp/protocol/__init__.py +1 -0
  39. synfig_mcp-1.0.3/src/synfig_mcp/protocol/client.py +272 -0
  40. synfig_mcp-1.0.3/src/synfig_mcp/protocol/fake_bridge.py +1146 -0
  41. synfig_mcp-1.0.3/src/synfig_mcp/protocol/models.py +212 -0
  42. synfig_mcp-1.0.3/src/synfig_mcp/recipes/__init__.py +19 -0
  43. synfig_mcp-1.0.3/src/synfig_mcp/recipes/engine.py +250 -0
  44. synfig_mcp-1.0.3/src/synfig_mcp/recipes/models.py +68 -0
  45. synfig_mcp-1.0.3/src/synfig_mcp/recipes/registry.py +370 -0
  46. synfig_mcp-1.0.3/src/synfig_mcp/recipes/styles.py +85 -0
  47. synfig_mcp-1.0.3/src/synfig_mcp/render/__init__.py +8 -0
  48. synfig_mcp-1.0.3/src/synfig_mcp/render/cache.py +52 -0
  49. synfig_mcp-1.0.3/src/synfig_mcp/render/jobs.py +166 -0
  50. synfig_mcp-1.0.3/src/synfig_mcp/render/pipeline.py +335 -0
  51. synfig_mcp-1.0.3/src/synfig_mcp/render/targets.py +128 -0
  52. synfig_mcp-1.0.3/src/synfig_mcp/resources/__init__.py +1 -0
  53. synfig_mcp-1.0.3/src/synfig_mcp/resources/guides.py +180 -0
  54. synfig_mcp-1.0.3/src/synfig_mcp/review/__init__.py +7 -0
  55. synfig_mcp-1.0.3/src/synfig_mcp/review/metrics.py +287 -0
  56. synfig_mcp-1.0.3/src/synfig_mcp/review/rubrics.py +85 -0
  57. synfig_mcp-1.0.3/src/synfig_mcp/review/service.py +227 -0
  58. synfig_mcp-1.0.3/src/synfig_mcp/schema/__init__.py +20 -0
  59. synfig_mcp-1.0.3/src/synfig_mcp/schema/cache.py +37 -0
  60. synfig_mcp-1.0.3/src/synfig_mcp/schema/param_types.json +566 -0
  61. synfig_mcp-1.0.3/src/synfig_mcp/schema/param_types.py +71 -0
  62. synfig_mcp-1.0.3/src/synfig_mcp/schema/probe.py +322 -0
  63. synfig_mcp-1.0.3/src/synfig_mcp/schema/registry.py +159 -0
  64. synfig_mcp-1.0.3/src/synfig_mcp/schema/scene_spec.py +61 -0
  65. synfig_mcp-1.0.3/src/synfig_mcp/security/__init__.py +1 -0
  66. synfig_mcp-1.0.3/src/synfig_mcp/security/sandbox.py +173 -0
  67. synfig_mcp-1.0.3/src/synfig_mcp/server.py +133 -0
  68. synfig_mcp-1.0.3/src/synfig_mcp/sif/__init__.py +23 -0
  69. synfig_mcp-1.0.3/src/synfig_mcp/sif/numerics.py +105 -0
  70. synfig_mcp-1.0.3/src/synfig_mcp/sif/parser.py +2213 -0
  71. synfig_mcp-1.0.3/src/synfig_mcp/sif/valuenodes.py +621 -0
  72. synfig_mcp-1.0.3/src/synfig_mcp/sif/writer.py +44 -0
  73. synfig_mcp-1.0.3/src/synfig_mcp/tools/__init__.py +5 -0
  74. synfig_mcp-1.0.3/src/synfig_mcp/tools/register.py +1704 -0
  75. synfig_mcp-1.0.3/src/synfig_mcp/validation.py +177 -0
  76. synfig_mcp-1.0.3/tests/__init__.py +1 -0
  77. synfig_mcp-1.0.3/tests/conftest.py +59 -0
  78. synfig_mcp-1.0.3/tests/contract/test_asset_pipeline.py +129 -0
  79. synfig_mcp-1.0.3/tests/contract/test_backend_parity.py +317 -0
  80. synfig_mcp-1.0.3/tests/contract/test_character_rig.py +227 -0
  81. synfig_mcp-1.0.3/tests/contract/test_client_fake_bridge.py +129 -0
  82. synfig_mcp-1.0.3/tests/contract/test_edge_cases.py +100 -0
  83. synfig_mcp-1.0.3/tests/contract/test_handshake.py +63 -0
  84. synfig_mcp-1.0.3/tests/contract/test_mcp_tools.py +737 -0
  85. synfig_mcp-1.0.3/tests/contract/test_recipe_apply.py +143 -0
  86. synfig_mcp-1.0.3/tests/contract/test_render_targets.py +117 -0
  87. synfig_mcp-1.0.3/tests/contract/test_review_tools.py +169 -0
  88. synfig_mcp-1.0.3/tests/contract/test_scene_auto_build.py +202 -0
  89. synfig_mcp-1.0.3/tests/contract/test_scene_build.py +213 -0
  90. synfig_mcp-1.0.3/tests/contract/test_schema_fixtures.py +61 -0
  91. synfig_mcp-1.0.3/tests/contract/test_sif_synfig_validity.py +734 -0
  92. synfig_mcp-1.0.3/tests/contract/test_tool_errors.py +28 -0
  93. synfig_mcp-1.0.3/tests/e2e/__init__.py +1 -0
  94. synfig_mcp-1.0.3/tests/e2e/conftest.py +160 -0
  95. synfig_mcp-1.0.3/tests/e2e/test_live_mutate.py +1874 -0
  96. synfig_mcp-1.0.3/tests/e2e/test_live_read.py +162 -0
  97. synfig_mcp-1.0.3/tests/e2e/test_live_scene_build.py +448 -0
  98. synfig_mcp-1.0.3/tests/e2e/test_shipped_release.py +161 -0
  99. synfig_mcp-1.0.3/tests/fixtures/sample_project.sif +57 -0
  100. synfig_mcp-1.0.3/tests/property/test_sif_lossless.py +147 -0
  101. synfig_mcp-1.0.3/tests/support/__init__.py +1 -0
  102. synfig_mcp-1.0.3/tests/support/rig_assets.py +71 -0
  103. synfig_mcp-1.0.3/tests/unit/test_assets_security.py +104 -0
  104. synfig_mcp-1.0.3/tests/unit/test_backend.py +42 -0
  105. synfig_mcp-1.0.3/tests/unit/test_backend_auto.py +144 -0
  106. synfig_mcp-1.0.3/tests/unit/test_backend_select.py +91 -0
  107. synfig_mcp-1.0.3/tests/unit/test_characters.py +94 -0
  108. synfig_mcp-1.0.3/tests/unit/test_cli_runner.py +57 -0
  109. synfig_mcp-1.0.3/tests/unit/test_config.py +55 -0
  110. synfig_mcp-1.0.3/tests/unit/test_coverage_matrix.py +145 -0
  111. synfig_mcp-1.0.3/tests/unit/test_cutout_rig.py +263 -0
  112. synfig_mcp-1.0.3/tests/unit/test_doctor.py +108 -0
  113. synfig_mcp-1.0.3/tests/unit/test_errors.py +25 -0
  114. synfig_mcp-1.0.3/tests/unit/test_headless_backend.py +292 -0
  115. synfig_mcp-1.0.3/tests/unit/test_http_transport.py +95 -0
  116. synfig_mcp-1.0.3/tests/unit/test_protocol_docs.py +102 -0
  117. synfig_mcp-1.0.3/tests/unit/test_protocol_models.py +119 -0
  118. synfig_mcp-1.0.3/tests/unit/test_recipes.py +80 -0
  119. synfig_mcp-1.0.3/tests/unit/test_render_pipeline.py +227 -0
  120. synfig_mcp-1.0.3/tests/unit/test_review_metrics.py +164 -0
  121. synfig_mcp-1.0.3/tests/unit/test_review_service.py +22 -0
  122. synfig_mcp-1.0.3/tests/unit/test_rig_sheet.py +105 -0
  123. synfig_mcp-1.0.3/tests/unit/test_sandbox.py +168 -0
  124. synfig_mcp-1.0.3/tests/unit/test_schema_registry.py +211 -0
  125. synfig_mcp-1.0.3/tests/unit/test_sif.py +15 -0
  126. synfig_mcp-1.0.3/tests/unit/test_sif_values.py +369 -0
  127. synfig_mcp-1.0.3/tests/unit/test_token_budget.py +109 -0
  128. synfig_mcp-1.0.3/tests/unit/test_tool_docs.py +72 -0
  129. 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
+ [![Server CI](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml/badge.svg?branch=v1.0.0)](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml)
51
+ [![PyPI](https://img.shields.io/badge/pypi-synfig--mcp-blue.svg)](https://pypi.org/project/synfig-mcp/)
52
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ [![Server CI](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml/badge.svg?branch=v1.0.0)](https://github.com/anusara-bemal/synfig-mcp/actions/workflows/server-ci.yml)
4
+ [![PyPI](https://img.shields.io/badge/pypi-synfig--mcp-blue.svg)](https://pypi.org/project/synfig-mcp/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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,5 @@
1
+ """
2
+ synfig-mcp: Live Model Context Protocol control of Synfig Studio.
3
+ """
4
+
5
+ __version__ = "0.8.0-rc2"
@@ -0,0 +1,8 @@
1
+ """
2
+ Entrypoint for python -m synfig_mcp.
3
+ """
4
+
5
+ from synfig_mcp.server import main
6
+
7
+ if __name__ == "__main__":
8
+ main()
@@ -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"