mcp-face-transform 2026.5.31.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.
- mcp_face_transform-2026.5.31.0/.env.example +16 -0
- mcp_face_transform-2026.5.31.0/.gitignore +82 -0
- mcp_face_transform-2026.5.31.0/CHANGELOG.md +28 -0
- mcp_face_transform-2026.5.31.0/LICENSE +21 -0
- mcp_face_transform-2026.5.31.0/PKG-INFO +175 -0
- mcp_face_transform-2026.5.31.0/README.md +120 -0
- mcp_face_transform-2026.5.31.0/core/__init__.py +15 -0
- mcp_face_transform-2026.5.31.0/core/client.py +160 -0
- mcp_face_transform-2026.5.31.0/core/config.py +65 -0
- mcp_face_transform-2026.5.31.0/core/exceptions.py +39 -0
- mcp_face_transform-2026.5.31.0/core/oauth.py +592 -0
- mcp_face_transform-2026.5.31.0/core/server.py +54 -0
- mcp_face_transform-2026.5.31.0/core/types.py +18 -0
- mcp_face_transform-2026.5.31.0/jetbrains/CHANGELOG.md +10 -0
- mcp_face_transform-2026.5.31.0/jetbrains/README.md +67 -0
- mcp_face_transform-2026.5.31.0/main.py +249 -0
- mcp_face_transform-2026.5.31.0/prompts/__init__.py +91 -0
- mcp_face_transform-2026.5.31.0/pyproject.toml +133 -0
- mcp_face_transform-2026.5.31.0/tests/__init__.py +1 -0
- mcp_face_transform-2026.5.31.0/tests/conftest.py +17 -0
- mcp_face_transform-2026.5.31.0/tests/test_smoke.py +64 -0
- mcp_face_transform-2026.5.31.0/tools/__init__.py +6 -0
- mcp_face_transform-2026.5.31.0/tools/face_tools.py +236 -0
- mcp_face_transform-2026.5.31.0/vscode/CHANGELOG.md +5 -0
- mcp_face_transform-2026.5.31.0/vscode/LICENSE +21 -0
- mcp_face_transform-2026.5.31.0/vscode/README.md +49 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Environment template - copy to .env and fill in your values
|
|
2
|
+
# Get your API token from https://platform.acedata.cloud
|
|
3
|
+
|
|
4
|
+
# AceDataCloud API Configuration (REQUIRED)
|
|
5
|
+
ACEDATACLOUD_API_TOKEN=your_api_token_here
|
|
6
|
+
ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
|
|
7
|
+
|
|
8
|
+
# Face Configuration (Optional)
|
|
9
|
+
FACE_REQUEST_TIMEOUT=180
|
|
10
|
+
|
|
11
|
+
# MCP Server Configuration (Optional)
|
|
12
|
+
MCP_SERVER_NAME=face
|
|
13
|
+
MCP_TRANSPORT=stdio
|
|
14
|
+
|
|
15
|
+
# Logging (Optional)
|
|
16
|
+
LOG_LEVEL=INFO
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
env.bak/
|
|
60
|
+
venv.bak/
|
|
61
|
+
|
|
62
|
+
# IDE
|
|
63
|
+
.idea/
|
|
64
|
+
.vscode/
|
|
65
|
+
*.swp
|
|
66
|
+
*.swo
|
|
67
|
+
*~
|
|
68
|
+
|
|
69
|
+
# Ruff
|
|
70
|
+
.ruff_cache/
|
|
71
|
+
|
|
72
|
+
# mypy
|
|
73
|
+
.mypy_cache/
|
|
74
|
+
.dmypy.json
|
|
75
|
+
dmypy.json
|
|
76
|
+
|
|
77
|
+
# OS
|
|
78
|
+
.DS_Store
|
|
79
|
+
Thumbs.db
|
|
80
|
+
|
|
81
|
+
# Project specific
|
|
82
|
+
*.log
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Calendar Versioning](https://calver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release of MCP Face Transform Server.
|
|
13
|
+
- Seven face tools wrapping the AceDataCloud Face Transform API:
|
|
14
|
+
- `face_detect_keypoints` — `POST /face/analyze` (90+ keypoints per face)
|
|
15
|
+
- `face_beautify` — `POST /face/beautify`
|
|
16
|
+
- `face_change_age` — `POST /face/change-age`
|
|
17
|
+
- `face_change_gender` — `POST /face/change-gender`
|
|
18
|
+
- `face_swap` — `POST /face/swap` (optional async `callback_url`)
|
|
19
|
+
- `face_cartoonize` — `POST /face/cartoon`
|
|
20
|
+
- `face_detect_liveness` — `POST /face/detect-live`
|
|
21
|
+
- `face_get_usage_guide` — concise tool reference
|
|
22
|
+
- Two MCP prompts: `face_guide`, `face_workflow_examples`.
|
|
23
|
+
- stdio and Streamable HTTP transports.
|
|
24
|
+
- OAuth 2.1 (PKCE) support for hosted Claude.ai deployment.
|
|
25
|
+
- Bearer-token authentication for direct HTTP access.
|
|
26
|
+
- JetBrains AI Assistant plugin scaffold and VS Code extension scaffold.
|
|
27
|
+
- GitHub Actions pipeline for PyPI / GitHub Release / MCP Registry / Smithery /
|
|
28
|
+
VS Code Marketplace / JetBrains Marketplace / Docker Hub publication.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AceDataCloud
|
|
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.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-face-transform
|
|
3
|
+
Version: 2026.5.31.0
|
|
4
|
+
Summary: MCP server for the AceDataCloud Face Transform API (keypoints, beautify, age/gender, swap, cartoon, liveness)
|
|
5
|
+
Project-URL: Homepage, https://github.com/AceDataCloud/FaceTransformMCP
|
|
6
|
+
Project-URL: Documentation, https://docs.acedata.cloud
|
|
7
|
+
Project-URL: Repository, https://github.com/AceDataCloud/FaceTransformMCP
|
|
8
|
+
Project-URL: Issues, https://github.com/AceDataCloud/FaceTransformMCP/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/AceDataCloud/FaceTransformMCP/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: AceDataCloud <support@acedata.cloud>
|
|
11
|
+
Maintainer-email: AceDataCloud <support@acedata.cloud>
|
|
12
|
+
License: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: acedata,beautify,claude,face,face-swap,face-transform,liveness-detection,llm,mcp,model-context-protocol
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Requires-Dist: loguru>=0.7.0
|
|
29
|
+
Requires-Dist: mcp>=1.2.0
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: build>=1.2.0; extra == 'all'
|
|
34
|
+
Requires-Dist: mypy>=1.10.0; extra == 'all'
|
|
35
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'all'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'all'
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == 'all'
|
|
39
|
+
Requires-Dist: respx>=0.21.0; extra == 'all'
|
|
40
|
+
Requires-Dist: ruff>=0.4.0; extra == 'all'
|
|
41
|
+
Requires-Dist: twine>=5.0.0; extra == 'all'
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
46
|
+
Provides-Extra: release
|
|
47
|
+
Requires-Dist: build>=1.2.0; extra == 'release'
|
|
48
|
+
Requires-Dist: twine>=5.0.0; extra == 'release'
|
|
49
|
+
Provides-Extra: test
|
|
50
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
51
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
52
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
53
|
+
Requires-Dist: respx>=0.21.0; extra == 'test'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# MCP Face Transform Server
|
|
57
|
+
|
|
58
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that
|
|
59
|
+
exposes the AceDataCloud Face Transform API — face keypoint detection,
|
|
60
|
+
beautification, age/gender transform, face swap, cartoonization, and liveness
|
|
61
|
+
detection.
|
|
62
|
+
|
|
63
|
+
> **Status:** All Face APIs are currently in **Alpha**. Interfaces may evolve.
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- **Keypoint detection** — 90+ landmarks per face, multi-face supported
|
|
68
|
+
- **Beautification** — smoothing, whitening, face slimming, eye enlarging
|
|
69
|
+
- **Age transform** — age or de-age a portrait
|
|
70
|
+
- **Gender transform** — swap perceived facial gender characteristics
|
|
71
|
+
- **Face swap** — move a source face onto a target image (with optional async webhook)
|
|
72
|
+
- **Cartoonize** — render a portrait in animated / cartoon style
|
|
73
|
+
- **Liveness detection** — distinguish live captures from printed / screen photos
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install mcp-face-transform
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Set your AceDataCloud API token:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export ACEDATACLOUD_API_TOKEN=your_token_here
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Get your token from [https://platform.acedata.cloud](https://platform.acedata.cloud).
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
### stdio mode (default)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
mcp-face-transform
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### HTTP mode
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
mcp-face-transform --transport http --port 8000
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Available Tools
|
|
106
|
+
|
|
107
|
+
| Tool | Endpoint | Purpose |
|
|
108
|
+
|------|----------|---------|
|
|
109
|
+
| `face_detect_keypoints` | `POST /face/analyze` | Detect 90+ keypoints per face |
|
|
110
|
+
| `face_beautify` | `POST /face/beautify` | Smoothing / whitening / slimming / eye enlarging |
|
|
111
|
+
| `face_change_age` | `POST /face/change-age` | Age or de-age a portrait |
|
|
112
|
+
| `face_change_gender` | `POST /face/change-gender` | Swap perceived facial gender characteristics |
|
|
113
|
+
| `face_swap` | `POST /face/swap` | Move source face onto target image |
|
|
114
|
+
| `face_cartoonize` | `POST /face/cartoon` | Convert portrait to cartoon style |
|
|
115
|
+
| `face_detect_liveness` | `POST /face/detect-live` | Detect live vs printed/screen face |
|
|
116
|
+
| `face_get_usage_guide` | _client-side_ | Concise tool usage reference |
|
|
117
|
+
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
"Detect all faces in https://example.com/group.jpg and return their keypoints."
|
|
122
|
+
→ face_detect_keypoints(image_url="https://example.com/group.jpg")
|
|
123
|
+
|
|
124
|
+
"Lighten and smooth my portrait."
|
|
125
|
+
→ face_beautify(image_url="https://example.com/me.jpg", smoothing=15, whitening=25)
|
|
126
|
+
|
|
127
|
+
"Replace the face in the scene with the headshot."
|
|
128
|
+
→ face_swap(
|
|
129
|
+
source_image_url="https://example.com/headshot.jpg",
|
|
130
|
+
target_image_url="https://example.com/scene.jpg",
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration in Claude Desktop / Claude Code
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"face-transform": {
|
|
140
|
+
"command": "uvx",
|
|
141
|
+
"args": ["mcp-face-transform"],
|
|
142
|
+
"env": {
|
|
143
|
+
"ACEDATACLOUD_API_TOKEN": "your_api_token_here"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or use the hosted endpoint with bearer auth:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"mcpServers": {
|
|
155
|
+
"face-transform": {
|
|
156
|
+
"url": "https://face.mcp.acedata.cloud/mcp",
|
|
157
|
+
"headers": {
|
|
158
|
+
"Authorization": "Bearer your_api_token_here"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pip install -e ".[dev,test]"
|
|
169
|
+
pytest --cov=core --cov=tools
|
|
170
|
+
ruff check .
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# MCP Face Transform Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that
|
|
4
|
+
exposes the AceDataCloud Face Transform API — face keypoint detection,
|
|
5
|
+
beautification, age/gender transform, face swap, cartoonization, and liveness
|
|
6
|
+
detection.
|
|
7
|
+
|
|
8
|
+
> **Status:** All Face APIs are currently in **Alpha**. Interfaces may evolve.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Keypoint detection** — 90+ landmarks per face, multi-face supported
|
|
13
|
+
- **Beautification** — smoothing, whitening, face slimming, eye enlarging
|
|
14
|
+
- **Age transform** — age or de-age a portrait
|
|
15
|
+
- **Gender transform** — swap perceived facial gender characteristics
|
|
16
|
+
- **Face swap** — move a source face onto a target image (with optional async webhook)
|
|
17
|
+
- **Cartoonize** — render a portrait in animated / cartoon style
|
|
18
|
+
- **Liveness detection** — distinguish live captures from printed / screen photos
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install mcp-face-transform
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
Set your AceDataCloud API token:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export ACEDATACLOUD_API_TOKEN=your_token_here
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Get your token from [https://platform.acedata.cloud](https://platform.acedata.cloud).
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### stdio mode (default)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mcp-face-transform
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### HTTP mode
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mcp-face-transform --transport http --port 8000
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Available Tools
|
|
51
|
+
|
|
52
|
+
| Tool | Endpoint | Purpose |
|
|
53
|
+
|------|----------|---------|
|
|
54
|
+
| `face_detect_keypoints` | `POST /face/analyze` | Detect 90+ keypoints per face |
|
|
55
|
+
| `face_beautify` | `POST /face/beautify` | Smoothing / whitening / slimming / eye enlarging |
|
|
56
|
+
| `face_change_age` | `POST /face/change-age` | Age or de-age a portrait |
|
|
57
|
+
| `face_change_gender` | `POST /face/change-gender` | Swap perceived facial gender characteristics |
|
|
58
|
+
| `face_swap` | `POST /face/swap` | Move source face onto target image |
|
|
59
|
+
| `face_cartoonize` | `POST /face/cartoon` | Convert portrait to cartoon style |
|
|
60
|
+
| `face_detect_liveness` | `POST /face/detect-live` | Detect live vs printed/screen face |
|
|
61
|
+
| `face_get_usage_guide` | _client-side_ | Concise tool usage reference |
|
|
62
|
+
|
|
63
|
+
## Example
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
"Detect all faces in https://example.com/group.jpg and return their keypoints."
|
|
67
|
+
→ face_detect_keypoints(image_url="https://example.com/group.jpg")
|
|
68
|
+
|
|
69
|
+
"Lighten and smooth my portrait."
|
|
70
|
+
→ face_beautify(image_url="https://example.com/me.jpg", smoothing=15, whitening=25)
|
|
71
|
+
|
|
72
|
+
"Replace the face in the scene with the headshot."
|
|
73
|
+
→ face_swap(
|
|
74
|
+
source_image_url="https://example.com/headshot.jpg",
|
|
75
|
+
target_image_url="https://example.com/scene.jpg",
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration in Claude Desktop / Claude Code
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"face-transform": {
|
|
85
|
+
"command": "uvx",
|
|
86
|
+
"args": ["mcp-face-transform"],
|
|
87
|
+
"env": {
|
|
88
|
+
"ACEDATACLOUD_API_TOKEN": "your_api_token_here"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or use the hosted endpoint with bearer auth:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"face-transform": {
|
|
101
|
+
"url": "https://face.mcp.acedata.cloud/mcp",
|
|
102
|
+
"headers": {
|
|
103
|
+
"Authorization": "Bearer your_api_token_here"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install -e ".[dev,test]"
|
|
114
|
+
pytest --cov=core --cov=tools
|
|
115
|
+
ruff check .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Core module for MCP Face Transform server."""
|
|
2
|
+
|
|
3
|
+
from core.client import FaceClient
|
|
4
|
+
from core.config import settings
|
|
5
|
+
from core.exceptions import FaceAPIError, FaceAuthError, FaceValidationError
|
|
6
|
+
from core.server import mcp
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"FaceClient",
|
|
10
|
+
"settings",
|
|
11
|
+
"mcp",
|
|
12
|
+
"FaceAPIError",
|
|
13
|
+
"FaceAuthError",
|
|
14
|
+
"FaceValidationError",
|
|
15
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""HTTP client for Face Transform API."""
|
|
2
|
+
|
|
3
|
+
import contextvars
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from core.config import settings
|
|
11
|
+
from core.exceptions import FaceAPIError, FaceAuthError, FaceError, FaceTimeoutError
|
|
12
|
+
|
|
13
|
+
# Context variable for per-request API token (used in HTTP/remote mode).
|
|
14
|
+
_request_api_token: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
15
|
+
"_request_api_token", default=None
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_request_api_token(token: str | None) -> None:
|
|
20
|
+
"""Set the API token for the current request context (HTTP mode)."""
|
|
21
|
+
_request_api_token.set(token)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_request_api_token() -> str | None:
|
|
25
|
+
"""Get the API token from the current request context."""
|
|
26
|
+
return _request_api_token.get()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FaceClient:
|
|
30
|
+
"""Async HTTP client for the AceDataCloud Face Transform API."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, api_token: str | None = None, base_url: str | None = None):
|
|
33
|
+
self.api_token = api_token if api_token is not None else settings.api_token
|
|
34
|
+
self.base_url = base_url or settings.api_base_url
|
|
35
|
+
self.timeout = settings.request_timeout
|
|
36
|
+
|
|
37
|
+
logger.info(f"FaceClient initialized with base_url: {self.base_url}")
|
|
38
|
+
logger.debug(f"API token configured: {'Yes' if self.api_token else 'No'}")
|
|
39
|
+
logger.debug(f"Request timeout: {self.timeout}s")
|
|
40
|
+
|
|
41
|
+
def _get_headers(self) -> dict[str, str]:
|
|
42
|
+
token = get_request_api_token() or self.api_token
|
|
43
|
+
if not token:
|
|
44
|
+
logger.error("API token not configured!")
|
|
45
|
+
raise FaceAuthError("API token not configured")
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
"accept": "application/json",
|
|
49
|
+
"authorization": f"Bearer {token}",
|
|
50
|
+
"content-type": "application/json",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
54
|
+
status = response.status_code
|
|
55
|
+
try:
|
|
56
|
+
body = response.json()
|
|
57
|
+
except Exception:
|
|
58
|
+
body = {}
|
|
59
|
+
|
|
60
|
+
error_obj = body.get("error", {}) if isinstance(body, dict) else {}
|
|
61
|
+
code = error_obj.get("code", f"http_{status}")
|
|
62
|
+
message = (
|
|
63
|
+
error_obj.get("message")
|
|
64
|
+
or (body.get("detail") if isinstance(body, dict) else None)
|
|
65
|
+
or response.text
|
|
66
|
+
or f"HTTP {status}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
logger.error(f"API error {status} [{code}]: {message}")
|
|
70
|
+
|
|
71
|
+
if status in (401, 403):
|
|
72
|
+
raise FaceAuthError(message)
|
|
73
|
+
raise FaceAPIError(message=message, code=code, status_code=status)
|
|
74
|
+
|
|
75
|
+
async def request(
|
|
76
|
+
self,
|
|
77
|
+
method: str,
|
|
78
|
+
endpoint: str,
|
|
79
|
+
*,
|
|
80
|
+
payload: dict[str, Any] | None = None,
|
|
81
|
+
params: dict[str, Any] | None = None,
|
|
82
|
+
timeout: float | None = None,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
method_upper = method.upper()
|
|
85
|
+
url = f"{self.base_url}{endpoint}"
|
|
86
|
+
request_timeout = timeout or self.timeout
|
|
87
|
+
|
|
88
|
+
logger.info(f"🚀 {method_upper} {url}")
|
|
89
|
+
if payload is not None:
|
|
90
|
+
logger.debug(f"Request payload: {json.dumps(payload, ensure_ascii=False, indent=2)}")
|
|
91
|
+
if params is not None:
|
|
92
|
+
logger.debug(f"Request params: {json.dumps(params, ensure_ascii=False, indent=2)}")
|
|
93
|
+
logger.debug(f"Timeout: {request_timeout}s")
|
|
94
|
+
|
|
95
|
+
async with httpx.AsyncClient() as http_client:
|
|
96
|
+
try:
|
|
97
|
+
response = await http_client.request(
|
|
98
|
+
method_upper,
|
|
99
|
+
url,
|
|
100
|
+
json=payload,
|
|
101
|
+
params=params,
|
|
102
|
+
headers=self._get_headers(),
|
|
103
|
+
timeout=request_timeout,
|
|
104
|
+
)
|
|
105
|
+
logger.info(f"📥 Response status: {response.status_code}")
|
|
106
|
+
|
|
107
|
+
if response.status_code >= 400:
|
|
108
|
+
self._handle_error_response(response)
|
|
109
|
+
|
|
110
|
+
result = response.json()
|
|
111
|
+
logger.success("✅ Request successful")
|
|
112
|
+
return result # type: ignore[no-any-return]
|
|
113
|
+
|
|
114
|
+
except httpx.TimeoutException as e:
|
|
115
|
+
logger.error(f"⏰ Request timeout after {request_timeout}s: {e}")
|
|
116
|
+
raise FaceTimeoutError(
|
|
117
|
+
f"Request to {endpoint} timed out after {request_timeout}s"
|
|
118
|
+
) from e
|
|
119
|
+
|
|
120
|
+
except FaceError:
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"❌ Request error: {e}")
|
|
125
|
+
raise FaceAPIError(message=str(e)) from e
|
|
126
|
+
|
|
127
|
+
async def analyze(self, **kwargs: Any) -> dict[str, Any]:
|
|
128
|
+
logger.info(f"🔍 Analyzing face keypoints for: {kwargs.get('image_url', '')}")
|
|
129
|
+
return await self.request("POST", "/face/analyze", payload=kwargs)
|
|
130
|
+
|
|
131
|
+
async def beautify(self, **kwargs: Any) -> dict[str, Any]:
|
|
132
|
+
logger.info(f"✨ Beautifying face for: {kwargs.get('image_url', '')}")
|
|
133
|
+
return await self.request("POST", "/face/beautify", payload=kwargs)
|
|
134
|
+
|
|
135
|
+
async def change_age(self, **kwargs: Any) -> dict[str, Any]:
|
|
136
|
+
logger.info(f"🕰️ Transforming age for: {kwargs.get('image_url', '')}")
|
|
137
|
+
return await self.request("POST", "/face/change-age", payload=kwargs)
|
|
138
|
+
|
|
139
|
+
async def change_gender(self, **kwargs: Any) -> dict[str, Any]:
|
|
140
|
+
logger.info(f"🔁 Swapping gender for: {kwargs.get('image_url', '')}")
|
|
141
|
+
return await self.request("POST", "/face/change-gender", payload=kwargs)
|
|
142
|
+
|
|
143
|
+
async def swap(self, **kwargs: Any) -> dict[str, Any]:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"🔄 Swapping face: source={kwargs.get('source_image_url', '')} "
|
|
146
|
+
f"target={kwargs.get('target_image_url', '')}"
|
|
147
|
+
)
|
|
148
|
+
return await self.request("POST", "/face/swap", payload=kwargs)
|
|
149
|
+
|
|
150
|
+
async def cartoon(self, **kwargs: Any) -> dict[str, Any]:
|
|
151
|
+
logger.info(f"🎨 Cartoonizing face for: {kwargs.get('image_url', '')}")
|
|
152
|
+
return await self.request("POST", "/face/cartoon", payload=kwargs)
|
|
153
|
+
|
|
154
|
+
async def detect_live(self, **kwargs: Any) -> dict[str, Any]:
|
|
155
|
+
logger.info(f"🛡️ Detecting liveness for: {kwargs.get('image_url', '')}")
|
|
156
|
+
return await self.request("POST", "/face/detect-live", payload=kwargs)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Global client instance
|
|
160
|
+
client = FaceClient()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Configuration management for MCP Face Transform server."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
# Load .env file from project root
|
|
10
|
+
_env_path = Path(__file__).parent.parent / ".env"
|
|
11
|
+
load_dotenv(dotenv_path=_env_path)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Settings:
|
|
16
|
+
"""Application settings loaded from environment variables."""
|
|
17
|
+
|
|
18
|
+
# API Configuration
|
|
19
|
+
api_base_url: str = field(
|
|
20
|
+
default_factory=lambda: os.getenv("ACEDATACLOUD_API_BASE_URL", "https://api.acedata.cloud")
|
|
21
|
+
)
|
|
22
|
+
api_token: str = field(default_factory=lambda: os.getenv("ACEDATACLOUD_API_TOKEN", ""))
|
|
23
|
+
|
|
24
|
+
# Request Configuration — face endpoints are synchronous; default 180s.
|
|
25
|
+
request_timeout: float = field(
|
|
26
|
+
default_factory=lambda: float(os.getenv("FACE_REQUEST_TIMEOUT", "180"))
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Server Configuration
|
|
30
|
+
server_name: str = field(default_factory=lambda: os.getenv("MCP_SERVER_NAME", "face"))
|
|
31
|
+
transport: str = field(default_factory=lambda: os.getenv("MCP_TRANSPORT", "stdio"))
|
|
32
|
+
log_level: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"))
|
|
33
|
+
|
|
34
|
+
# OAuth / Remote Auth Configuration
|
|
35
|
+
server_url: str = field(default_factory=lambda: os.getenv("MCP_SERVER_URL", ""))
|
|
36
|
+
auth_base_url: str = field(
|
|
37
|
+
default_factory=lambda: os.getenv(
|
|
38
|
+
"ACEDATACLOUD_AUTH_BASE_URL", "https://auth.acedata.cloud"
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
platform_base_url: str = field(
|
|
42
|
+
default_factory=lambda: os.getenv(
|
|
43
|
+
"ACEDATACLOUD_PLATFORM_BASE_URL", "https://platform.acedata.cloud"
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
oauth_client_id: str = field(
|
|
47
|
+
default_factory=lambda: os.getenv("ACEDATACLOUD_OAUTH_CLIENT_ID", "")
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def validate(self) -> None:
|
|
51
|
+
"""Validate required settings."""
|
|
52
|
+
if not self.api_token:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"ACEDATACLOUD_API_TOKEN environment variable is required."
|
|
55
|
+
"Get your token from https://platform.acedata.cloud"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def is_configured(self) -> bool:
|
|
60
|
+
"""Check if the API token is configured."""
|
|
61
|
+
return bool(self.api_token)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Global settings instance
|
|
65
|
+
settings = Settings()
|