turbogen-mcp 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.
- turbogen_mcp-0.1.0/.gitignore +10 -0
- turbogen_mcp-0.1.0/LICENSE +21 -0
- turbogen_mcp-0.1.0/PKG-INFO +153 -0
- turbogen_mcp-0.1.0/README.md +133 -0
- turbogen_mcp-0.1.0/README.md.bak1111 +129 -0
- turbogen_mcp-0.1.0/pyproject.toml +35 -0
- turbogen_mcp-0.1.0/server.json +20 -0
- turbogen_mcp-0.1.0/server.py +378 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Turbogen
|
|
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,153 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: turbogen-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for generating images, video and audio through the Turbogen API
|
|
5
|
+
Project-URL: Homepage, https://turbogen.app
|
|
6
|
+
Project-URL: Documentation, https://turbogen.app/api-docs#mcp
|
|
7
|
+
Project-URL: Pricing, https://turbogen.app/pricing
|
|
8
|
+
Author-email: Turbogen <hello@turbogen.app>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-image,ai-video,claude,kling,mcp,model-context-protocol,nano-banana,seedance,veo
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
15
|
+
Classifier: Topic :: Multimedia :: Video
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: httpx>=0.27
|
|
18
|
+
Requires-Dist: mcp>=1.2
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Turbogen MCP server
|
|
22
|
+
|
|
23
|
+
<!-- mcp-name: app.turbogen/turbogen -->
|
|
24
|
+
|
|
25
|
+
Generate images, video and audio with Turbogen straight from Claude Desktop, Claude Code, Cursor or any
|
|
26
|
+
other MCP client. Same models and prices as the studio; credits come from your balance.
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
| Tool | What it does |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `list_models(kind?)` | Model ids, aspect/voice choices, options and credit prices (`kind` = image, video or audio) |
|
|
33
|
+
| `generate_image(prompt, model="nano-banana-2", aspect="1:1", option?, reference_images?)` | Image, saved locally; a preview is returned when the file is under 1 MB |
|
|
34
|
+
| `generate_video(prompt, model="veo-3.1-fast", aspect="16:9", option="8s", image_path?)` | Video clip; pass `image_path` for image-to-video |
|
|
35
|
+
| `generate_audio(text_or_prompt, model="voice", aspect?, option?, lyrics?)` | `voice` (aspect = voice name), `music` (aspect = Instrumental / With vocals + lyrics) or `sfx` |
|
|
36
|
+
| `upscale_image(path, scale="2x")` | Upscale a local image 2x or 4x |
|
|
37
|
+
| `get_balance()` | Remaining credits |
|
|
38
|
+
| `job_status(job_id, download=false)` | Status of a job, e.g. one that took longer than the 10-minute wait |
|
|
39
|
+
|
|
40
|
+
Generation tools submit the job, poll every 3 s for up to 10 minutes, download the result into the output
|
|
41
|
+
folder and return the local path, the cost and your remaining credits.
|
|
42
|
+
|
|
43
|
+
## Get an API key
|
|
44
|
+
|
|
45
|
+
API access comes with the Boost, Turbo and Max packs. Open **API** in the studio
|
|
46
|
+
(`https://turbogen.app/#/apikeys`), create a key and copy it (`lk_...`, shown only once).
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
| Variable | Required | Default |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `TURBOGEN_API_KEY` | yes | — |
|
|
53
|
+
| `TURBOGEN_BASE_URL` | no | `https://turbogen.app` |
|
|
54
|
+
| `TURBOGEN_OUTPUT_DIR` | no | `~/Downloads/turbogen` |
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
With [uv](https://docs.astral.sh/uv/) nothing needs installing up front, `uvx` runs it on demand:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uvx turbogen-mcp
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or install the command once with pipx (or uv tool):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pipx install turbogen-mcp # or: uv tool install turbogen-mcp
|
|
68
|
+
turbogen-mcp # waits for an MCP client on stdin; Ctrl+C to quit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Plain venv:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m venv .venv
|
|
75
|
+
.venv/bin/pip install turbogen-mcp # Windows: .venv\Scripts\pip install turbogen-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Requires Python 3.10+.
|
|
79
|
+
|
|
80
|
+
## Claude Desktop
|
|
81
|
+
|
|
82
|
+
Edit `claude_desktop_config.json` (Settings > Developer > Edit Config; macOS:
|
|
83
|
+
`~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`) and restart Claude Desktop:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"turbogen": {
|
|
89
|
+
"command": "uvx",
|
|
90
|
+
"args": ["turbogen-mcp"],
|
|
91
|
+
"env": {
|
|
92
|
+
"TURBOGEN_API_KEY": "lk_your_key_here"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
With pipx use `"command": "turbogen-mcp"` and drop `args`. If the app cannot find the command, give the full
|
|
100
|
+
path (e.g. `C:\\Users\\you\\.local\\bin\\turbogen-mcp.exe`).
|
|
101
|
+
|
|
102
|
+
## Claude Code
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- uvx turbogen-mcp
|
|
106
|
+
# installed with pipx instead:
|
|
107
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- turbogen-mcp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Add `--scope user` to make it available in every project. Check with `claude mcp list`.
|
|
111
|
+
|
|
112
|
+
## Cursor
|
|
113
|
+
|
|
114
|
+
Create `.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for all projects):
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"turbogen": {
|
|
120
|
+
"command": "uvx",
|
|
121
|
+
"args": ["turbogen-mcp"],
|
|
122
|
+
"env": {
|
|
123
|
+
"TURBOGEN_API_KEY": "lk_your_key_here",
|
|
124
|
+
"TURBOGEN_OUTPUT_DIR": "~/Downloads/turbogen"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Try it
|
|
132
|
+
|
|
133
|
+
Ask your assistant things like:
|
|
134
|
+
|
|
135
|
+
- "List the Turbogen video models and their prices."
|
|
136
|
+
- "Generate a 16:9 image of a lighthouse at dusk with nano-banana-2."
|
|
137
|
+
- "Turn ~/Downloads/turbogen/lighthouse.png into an 8 second Veo clip with a slow push-in."
|
|
138
|
+
- "Make a 5 second sound effect of waves on rocks."
|
|
139
|
+
- "How many credits do I have left?"
|
|
140
|
+
|
|
141
|
+
## Errors
|
|
142
|
+
|
|
143
|
+
- *Not enough credits* (402): top up at `<base>/#/pricing`.
|
|
144
|
+
- *Invalid API key* (401): the key is wrong, revoked, or the account has no API access.
|
|
145
|
+
- *Model unavailable* (503): try another model or retry later.
|
|
146
|
+
- *Rate limited* (429): up to 30 jobs per minute and 3 running at once.
|
|
147
|
+
- Failed generations are refunded automatically.
|
|
148
|
+
|
|
149
|
+
## Links
|
|
150
|
+
|
|
151
|
+
- Website and pricing: https://turbogen.app/pricing
|
|
152
|
+
- API reference: https://turbogen.app/api-docs
|
|
153
|
+
- Support: hello@turbogen.app
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Turbogen MCP server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: app.turbogen/turbogen -->
|
|
4
|
+
|
|
5
|
+
Generate images, video and audio with Turbogen straight from Claude Desktop, Claude Code, Cursor or any
|
|
6
|
+
other MCP client. Same models and prices as the studio; credits come from your balance.
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
| Tool | What it does |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `list_models(kind?)` | Model ids, aspect/voice choices, options and credit prices (`kind` = image, video or audio) |
|
|
13
|
+
| `generate_image(prompt, model="nano-banana-2", aspect="1:1", option?, reference_images?)` | Image, saved locally; a preview is returned when the file is under 1 MB |
|
|
14
|
+
| `generate_video(prompt, model="veo-3.1-fast", aspect="16:9", option="8s", image_path?)` | Video clip; pass `image_path` for image-to-video |
|
|
15
|
+
| `generate_audio(text_or_prompt, model="voice", aspect?, option?, lyrics?)` | `voice` (aspect = voice name), `music` (aspect = Instrumental / With vocals + lyrics) or `sfx` |
|
|
16
|
+
| `upscale_image(path, scale="2x")` | Upscale a local image 2x or 4x |
|
|
17
|
+
| `get_balance()` | Remaining credits |
|
|
18
|
+
| `job_status(job_id, download=false)` | Status of a job, e.g. one that took longer than the 10-minute wait |
|
|
19
|
+
|
|
20
|
+
Generation tools submit the job, poll every 3 s for up to 10 minutes, download the result into the output
|
|
21
|
+
folder and return the local path, the cost and your remaining credits.
|
|
22
|
+
|
|
23
|
+
## Get an API key
|
|
24
|
+
|
|
25
|
+
API access comes with the Boost, Turbo and Max packs. Open **API** in the studio
|
|
26
|
+
(`https://turbogen.app/#/apikeys`), create a key and copy it (`lk_...`, shown only once).
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
| Variable | Required | Default |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `TURBOGEN_API_KEY` | yes | — |
|
|
33
|
+
| `TURBOGEN_BASE_URL` | no | `https://turbogen.app` |
|
|
34
|
+
| `TURBOGEN_OUTPUT_DIR` | no | `~/Downloads/turbogen` |
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
With [uv](https://docs.astral.sh/uv/) nothing needs installing up front, `uvx` runs it on demand:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx turbogen-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install the command once with pipx (or uv tool):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pipx install turbogen-mcp # or: uv tool install turbogen-mcp
|
|
48
|
+
turbogen-mcp # waits for an MCP client on stdin; Ctrl+C to quit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Plain venv:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m venv .venv
|
|
55
|
+
.venv/bin/pip install turbogen-mcp # Windows: .venv\Scripts\pip install turbogen-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Requires Python 3.10+.
|
|
59
|
+
|
|
60
|
+
## Claude Desktop
|
|
61
|
+
|
|
62
|
+
Edit `claude_desktop_config.json` (Settings > Developer > Edit Config; macOS:
|
|
63
|
+
`~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`) and restart Claude Desktop:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"turbogen": {
|
|
69
|
+
"command": "uvx",
|
|
70
|
+
"args": ["turbogen-mcp"],
|
|
71
|
+
"env": {
|
|
72
|
+
"TURBOGEN_API_KEY": "lk_your_key_here"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With pipx use `"command": "turbogen-mcp"` and drop `args`. If the app cannot find the command, give the full
|
|
80
|
+
path (e.g. `C:\\Users\\you\\.local\\bin\\turbogen-mcp.exe`).
|
|
81
|
+
|
|
82
|
+
## Claude Code
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- uvx turbogen-mcp
|
|
86
|
+
# installed with pipx instead:
|
|
87
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- turbogen-mcp
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Add `--scope user` to make it available in every project. Check with `claude mcp list`.
|
|
91
|
+
|
|
92
|
+
## Cursor
|
|
93
|
+
|
|
94
|
+
Create `.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for all projects):
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"turbogen": {
|
|
100
|
+
"command": "uvx",
|
|
101
|
+
"args": ["turbogen-mcp"],
|
|
102
|
+
"env": {
|
|
103
|
+
"TURBOGEN_API_KEY": "lk_your_key_here",
|
|
104
|
+
"TURBOGEN_OUTPUT_DIR": "~/Downloads/turbogen"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Try it
|
|
112
|
+
|
|
113
|
+
Ask your assistant things like:
|
|
114
|
+
|
|
115
|
+
- "List the Turbogen video models and their prices."
|
|
116
|
+
- "Generate a 16:9 image of a lighthouse at dusk with nano-banana-2."
|
|
117
|
+
- "Turn ~/Downloads/turbogen/lighthouse.png into an 8 second Veo clip with a slow push-in."
|
|
118
|
+
- "Make a 5 second sound effect of waves on rocks."
|
|
119
|
+
- "How many credits do I have left?"
|
|
120
|
+
|
|
121
|
+
## Errors
|
|
122
|
+
|
|
123
|
+
- *Not enough credits* (402): top up at `<base>/#/pricing`.
|
|
124
|
+
- *Invalid API key* (401): the key is wrong, revoked, or the account has no API access.
|
|
125
|
+
- *Model unavailable* (503): try another model or retry later.
|
|
126
|
+
- *Rate limited* (429): up to 30 jobs per minute and 3 running at once.
|
|
127
|
+
- Failed generations are refunded automatically.
|
|
128
|
+
|
|
129
|
+
## Links
|
|
130
|
+
|
|
131
|
+
- Website and pricing: https://turbogen.app/pricing
|
|
132
|
+
- API reference: https://turbogen.app/api-docs
|
|
133
|
+
- Support: hello@turbogen.app
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Turbogen MCP server
|
|
2
|
+
|
|
3
|
+
Generate images, video and audio with Turbogen straight from Claude Desktop, Claude Code, Cursor or any
|
|
4
|
+
other MCP client. Same models and prices as the studio; credits come from your balance.
|
|
5
|
+
|
|
6
|
+
## Tools
|
|
7
|
+
|
|
8
|
+
| Tool | What it does |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `list_models(kind?)` | Model ids, aspect/voice choices, options and credit prices (`kind` = image, video or audio) |
|
|
11
|
+
| `generate_image(prompt, model="nano-banana-2", aspect="1:1", option?, reference_images?)` | Image, saved locally; a preview is returned when the file is under 1 MB |
|
|
12
|
+
| `generate_video(prompt, model="veo-3.1-fast", aspect="16:9", option="8s", image_path?)` | Video clip; pass `image_path` for image-to-video |
|
|
13
|
+
| `generate_audio(text_or_prompt, model="voice", aspect?, option?, lyrics?)` | `voice` (aspect = voice name), `music` (aspect = Instrumental / With vocals + lyrics) or `sfx` |
|
|
14
|
+
| `upscale_image(path, scale="2x")` | Upscale a local image 2x or 4x |
|
|
15
|
+
| `get_balance()` | Remaining credits |
|
|
16
|
+
| `job_status(job_id, download=false)` | Status of a job, e.g. one that took longer than the 10-minute wait |
|
|
17
|
+
|
|
18
|
+
Generation tools submit the job, poll every 3 s for up to 10 minutes, download the result into the output
|
|
19
|
+
folder and return the local path, the cost and your remaining credits.
|
|
20
|
+
|
|
21
|
+
## Get an API key
|
|
22
|
+
|
|
23
|
+
API access comes with the Boost, Turbo and Max packs. Open **API** in the studio
|
|
24
|
+
(`https://turbogen.app/#/apikeys`), create a key and copy it (`lk_...`, shown only once).
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
| Variable | Required | Default |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `TURBOGEN_API_KEY` | yes | — |
|
|
31
|
+
| `TURBOGEN_BASE_URL` | no | `https://turbogen.app` |
|
|
32
|
+
| `TURBOGEN_OUTPUT_DIR` | no | `~/Downloads/turbogen` |
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
With [uv](https://docs.astral.sh/uv/) nothing needs installing up front, `uvx` runs it on demand:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uvx --from /path/to/mcp-server turbogen-mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or install the command once with pipx (or uv tool):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pipx install /path/to/mcp-server # or: uv tool install /path/to/mcp-server
|
|
46
|
+
turbogen-mcp # waits for an MCP client on stdin; Ctrl+C to quit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Plain venv:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m venv .venv
|
|
53
|
+
.venv/bin/pip install . # Windows: .venv\Scripts\pip install .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires Python 3.10+.
|
|
57
|
+
|
|
58
|
+
## Claude Desktop
|
|
59
|
+
|
|
60
|
+
Edit `claude_desktop_config.json` (Settings > Developer > Edit Config; macOS:
|
|
61
|
+
`~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`) and restart Claude Desktop:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"turbogen": {
|
|
67
|
+
"command": "uvx",
|
|
68
|
+
"args": ["--from", "/path/to/mcp-server", "turbogen-mcp"],
|
|
69
|
+
"env": {
|
|
70
|
+
"TURBOGEN_API_KEY": "lk_your_key_here"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With pipx use `"command": "turbogen-mcp"` and drop `args`. If the app cannot find the command, give the full
|
|
78
|
+
path (e.g. `C:\\Users\\you\\.local\\bin\\turbogen-mcp.exe`).
|
|
79
|
+
|
|
80
|
+
## Claude Code
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- uvx --from /path/to/mcp-server turbogen-mcp
|
|
84
|
+
# installed with pipx instead:
|
|
85
|
+
claude mcp add turbogen --env TURBOGEN_API_KEY=lk_your_key_here -- turbogen-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Add `--scope user` to make it available in every project. Check with `claude mcp list`.
|
|
89
|
+
|
|
90
|
+
## Cursor
|
|
91
|
+
|
|
92
|
+
Create `.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for all projects):
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"turbogen": {
|
|
98
|
+
"command": "uvx",
|
|
99
|
+
"args": ["--from", "/path/to/mcp-server", "turbogen-mcp"],
|
|
100
|
+
"env": {
|
|
101
|
+
"TURBOGEN_API_KEY": "lk_your_key_here",
|
|
102
|
+
"TURBOGEN_OUTPUT_DIR": "~/Downloads/turbogen"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Try it
|
|
110
|
+
|
|
111
|
+
Ask your assistant things like:
|
|
112
|
+
|
|
113
|
+
- "List the Turbogen video models and their prices."
|
|
114
|
+
- "Generate a 16:9 image of a lighthouse at dusk with nano-banana-2."
|
|
115
|
+
- "Turn ~/Downloads/turbogen/lighthouse.png into an 8 second Veo clip with a slow push-in."
|
|
116
|
+
- "Make a 5 second sound effect of waves on rocks."
|
|
117
|
+
- "How many credits do I have left?"
|
|
118
|
+
|
|
119
|
+
## Errors
|
|
120
|
+
|
|
121
|
+
- *Not enough credits* (402): top up at `<base>/#/pricing`.
|
|
122
|
+
- *Invalid API key* (401): the key is wrong, revoked, or the account has no API access.
|
|
123
|
+
- *Model unavailable* (503): try another model or retry later.
|
|
124
|
+
- *Rate limited* (429): up to 30 jobs per minute and 3 running at once.
|
|
125
|
+
- Failed generations are refunded automatically.
|
|
126
|
+
|
|
127
|
+
## Rebranding
|
|
128
|
+
|
|
129
|
+
The brand name and default host are the `BRAND` and `DEFAULT_BASE_URL` constants at the top of `server.py`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "turbogen-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for generating images, video and audio through the Turbogen API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Turbogen", email = "hello@turbogen.app" }]
|
|
14
|
+
keywords = ["mcp", "model-context-protocol", "ai-video", "ai-image", "veo", "seedance", "kling", "nano-banana", "claude"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Multimedia :: Video",
|
|
19
|
+
"Topic :: Multimedia :: Graphics",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.2",
|
|
23
|
+
"httpx>=0.27",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://turbogen.app"
|
|
28
|
+
Documentation = "https://turbogen.app/api-docs#mcp"
|
|
29
|
+
Pricing = "https://turbogen.app/pricing"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
turbogen-mcp = "server:main"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
only-include = ["server.py", "LICENSE"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "app.turbogen/turbogen",
|
|
4
|
+
"title": "Turbogen",
|
|
5
|
+
"description": "Generate images, video and audio (Veo 3.1, Seedance, Kling, Nano Banana Pro) pay-as-you-go.",
|
|
6
|
+
"websiteUrl": "https://turbogen.app/api-docs#mcp",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"packages": [
|
|
9
|
+
{
|
|
10
|
+
"registryType": "pypi",
|
|
11
|
+
"identifier": "turbogen-mcp",
|
|
12
|
+
"version": "0.1.0",
|
|
13
|
+
"transport": { "type": "stdio" },
|
|
14
|
+
"environmentVariables": [
|
|
15
|
+
{ "name": "TURBOGEN_API_KEY", "description": "API key from https://turbogen.app/#/apikeys (starts with lk_)", "isRequired": true, "isSecret": true },
|
|
16
|
+
{ "name": "TURBOGEN_OUTPUT_DIR", "description": "Folder for downloaded results (default ~/Downloads/turbogen)", "isRequired": false }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Turbogen MCP server: generate images, video and audio through the Turbogen API from any MCP client.
|
|
2
|
+
|
|
3
|
+
Config (environment):
|
|
4
|
+
TURBOGEN_API_KEY required, an API key starting with lk_
|
|
5
|
+
TURBOGEN_BASE_URL optional, defaults to DEFAULT_BASE_URL
|
|
6
|
+
TURBOGEN_OUTPUT_DIR optional, defaults to ~/Downloads/turbogen
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import mimetypes
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
try: # mcp 1.x
|
|
20
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
21
|
+
except ImportError: # mcp 2.x renamed FastMCP -> MCPServer (same decorator API)
|
|
22
|
+
from mcp.server.mcpserver import Image, MCPServer as FastMCP
|
|
23
|
+
|
|
24
|
+
# Brand / host live here only - change these two lines on a rename.
|
|
25
|
+
BRAND = "Turbogen"
|
|
26
|
+
DEFAULT_BASE_URL = "https://turbogen.app"
|
|
27
|
+
|
|
28
|
+
POLL_EVERY_S = 3
|
|
29
|
+
POLL_TIMEOUT_S = 600
|
|
30
|
+
INLINE_IMAGE_MAX_BYTES = 1_000_000
|
|
31
|
+
|
|
32
|
+
mcp = FastMCP(f"{BRAND.lower()}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------- config & http ----------
|
|
36
|
+
|
|
37
|
+
class TurbogenError(Exception):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _base() -> str:
|
|
42
|
+
return os.environ.get("TURBOGEN_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _key() -> str:
|
|
46
|
+
k = os.environ.get("TURBOGEN_API_KEY", "").strip()
|
|
47
|
+
if not k:
|
|
48
|
+
raise TurbogenError("TURBOGEN_API_KEY is not set. Create a key on the API page of "
|
|
49
|
+
f"{_base()}/#/apikeys and add it to the MCP server config.")
|
|
50
|
+
return k
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _out_dir() -> Path:
|
|
54
|
+
d = Path(os.environ.get("TURBOGEN_OUTPUT_DIR") or Path.home() / "Downloads" / BRAND.lower()).expanduser()
|
|
55
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
return d
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _client(auth: bool = True) -> httpx.AsyncClient:
|
|
60
|
+
headers = {"User-Agent": f"{BRAND.lower()}-mcp/0.1"}
|
|
61
|
+
if auth:
|
|
62
|
+
headers["Authorization"] = f"Bearer {_key()}"
|
|
63
|
+
return httpx.AsyncClient(base_url=_base(), headers=headers, timeout=httpx.Timeout(120, connect=15),
|
|
64
|
+
follow_redirects=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _raise_for(r: httpx.Response) -> None:
|
|
68
|
+
if r.is_success:
|
|
69
|
+
return
|
|
70
|
+
try:
|
|
71
|
+
detail = r.json().get("detail")
|
|
72
|
+
except Exception:
|
|
73
|
+
detail = None
|
|
74
|
+
detail = detail if isinstance(detail, str) else (r.text[:300] or r.reason_phrase)
|
|
75
|
+
if r.status_code == 402:
|
|
76
|
+
raise TurbogenError(f"Not enough credits, top up at {_base()}/#/pricing")
|
|
77
|
+
if r.status_code == 401:
|
|
78
|
+
raise TurbogenError(f"Invalid API key ({detail}). Check TURBOGEN_API_KEY; keys are managed at {_base()}/#/apikeys")
|
|
79
|
+
if r.status_code == 503:
|
|
80
|
+
raise TurbogenError(f"Model unavailable: {detail}")
|
|
81
|
+
if r.status_code == 429:
|
|
82
|
+
raise TurbogenError(f"Rate limited: {detail}")
|
|
83
|
+
raise TurbogenError(f"API error {r.status_code}: {detail}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_models_cache: dict[str, Any] = {"t": 0.0, "data": None}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def _catalog() -> dict:
|
|
90
|
+
if _models_cache["data"] and time.time() - _models_cache["t"] < 300:
|
|
91
|
+
return _models_cache["data"]
|
|
92
|
+
# /api/models is public; API keys are accepted on /api/jobs only.
|
|
93
|
+
async with _client(auth=False) as c:
|
|
94
|
+
r = await c.get("/api/models")
|
|
95
|
+
_raise_for(r)
|
|
96
|
+
_models_cache.update(t=time.time(), data=r.json())
|
|
97
|
+
return _models_cache["data"]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def _model(model_id: str, kind: str | None = None) -> dict:
|
|
101
|
+
cat = await _catalog()
|
|
102
|
+
for m in cat["models"]:
|
|
103
|
+
if m["id"] == model_id:
|
|
104
|
+
if kind and m["kind"] != kind:
|
|
105
|
+
raise TurbogenError(f"'{model_id}' is a {m['kind']} model, not {kind}. Use list_models(kind='{kind}').")
|
|
106
|
+
return m
|
|
107
|
+
ids = ", ".join(m["id"] for m in cat["models"] if not kind or m["kind"] == kind)
|
|
108
|
+
raise TurbogenError(f"Unknown model '{model_id}'. Available: {ids}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _pick(value: str | None, allowed: list[str], label: str, model_id: str) -> str:
|
|
112
|
+
if not value:
|
|
113
|
+
return allowed[0]
|
|
114
|
+
if value in allowed:
|
|
115
|
+
return value
|
|
116
|
+
for a in allowed: # forgiving case-insensitive match ("4k" -> "4K")
|
|
117
|
+
if a.lower() == value.lower():
|
|
118
|
+
return a
|
|
119
|
+
raise TurbogenError(f"{label} '{value}' is not supported by {model_id}. Choose one of: {', '.join(allowed)}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _ref_file(path: str) -> tuple[str, bytes, str]:
|
|
123
|
+
p = Path(path).expanduser()
|
|
124
|
+
if not p.is_file():
|
|
125
|
+
raise TurbogenError(f"File not found: {p}")
|
|
126
|
+
mime = mimetypes.guess_type(p.name)[0] or "application/octet-stream"
|
|
127
|
+
if mime not in ("image/png", "image/jpeg", "image/webp"):
|
|
128
|
+
raise TurbogenError(f"{p.name}: only PNG, JPEG or WEBP images are accepted")
|
|
129
|
+
return (p.name, p.read_bytes(), mime)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------- job flow ----------
|
|
133
|
+
|
|
134
|
+
async def _find_job(c: httpx.AsyncClient, job_id: str) -> tuple[dict | None, int]:
|
|
135
|
+
r = await c.get("/api/jobs", params={"limit": 200})
|
|
136
|
+
_raise_for(r)
|
|
137
|
+
data = r.json()
|
|
138
|
+
return next((j for j in data["jobs"] if j["id"] == job_id), None), data.get("credits")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _slug(s: str, n: int = 40) -> str:
|
|
142
|
+
s = re.sub(r"[^A-Za-z0-9]+", "-", s).strip("-").lower()
|
|
143
|
+
return s[:n].strip("-") or "result"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def _run_job(fields: dict[str, str], refs: list[str], label: str) -> dict:
|
|
147
|
+
files = [("refs", _ref_file(p)) for p in refs]
|
|
148
|
+
async with _client() as c:
|
|
149
|
+
r = await c.post("/api/jobs", data=fields, files=files or None)
|
|
150
|
+
_raise_for(r)
|
|
151
|
+
sub = r.json()
|
|
152
|
+
job = sub["job"]
|
|
153
|
+
credits = sub.get("credits")
|
|
154
|
+
deadline = time.monotonic() + POLL_TIMEOUT_S
|
|
155
|
+
while job["status"] in ("queued", "running"):
|
|
156
|
+
if time.monotonic() > deadline:
|
|
157
|
+
raise TurbogenError(f"Job {job['id']} still {job['status']} after {POLL_TIMEOUT_S // 60} min. "
|
|
158
|
+
f"Check later with job_status('{job['id']}').")
|
|
159
|
+
await asyncio.sleep(POLL_EVERY_S)
|
|
160
|
+
found, credits = await _find_job(c, job["id"])
|
|
161
|
+
if found:
|
|
162
|
+
job = found
|
|
163
|
+
if job["status"] != "done":
|
|
164
|
+
raise TurbogenError(f"Generation failed: {job.get('note') or 'no reason given'} "
|
|
165
|
+
f"(job {job['id']}; failed jobs are refunded automatically). Credits: {credits}")
|
|
166
|
+
path = await _download(c, job, label)
|
|
167
|
+
return {"job_id": job["id"], "path": str(path), "mime": job.get("mime"), "cost": job.get("cost"),
|
|
168
|
+
"credits_left": credits}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def _download(c: httpx.AsyncClient, job: dict, label: str) -> Path:
|
|
172
|
+
r = await c.get(job["url"] or f"/api/jobs/{job['id']}/file")
|
|
173
|
+
_raise_for(r)
|
|
174
|
+
mime = (job.get("mime") or r.headers.get("content-type", "")).split(";")[0]
|
|
175
|
+
ext = {"image/jpeg": ".jpg", "audio/mpeg": ".mp3"}.get(mime) or mimetypes.guess_extension(mime) or ".bin"
|
|
176
|
+
path = _out_dir() / f"{job['model']}-{_slug(label)}-{job['id'][:8]}{ext}"
|
|
177
|
+
path.write_bytes(r.content)
|
|
178
|
+
return path
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _summary(res: dict) -> str:
|
|
182
|
+
return (f"Saved: {res['path']}\nCost: {res['cost']} credits · Credits left: {res['credits_left']}"
|
|
183
|
+
f"\nJob: {res['job_id']}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _with_image(res: dict) -> list:
|
|
187
|
+
out: list = [_summary(res)]
|
|
188
|
+
p = Path(res["path"])
|
|
189
|
+
if (res.get("mime") or "").startswith("image/") and p.stat().st_size <= INLINE_IMAGE_MAX_BYTES:
|
|
190
|
+
out.append(Image(path=p))
|
|
191
|
+
return out
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _err(e: Exception) -> str:
|
|
195
|
+
if isinstance(e, TurbogenError):
|
|
196
|
+
return f"Error: {e}"
|
|
197
|
+
if isinstance(e, httpx.HTTPError):
|
|
198
|
+
return f"Error: could not reach {_base()} ({e.__class__.__name__}: {e})"
|
|
199
|
+
raise e
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ---------- tools ----------
|
|
203
|
+
|
|
204
|
+
@mcp.tool()
|
|
205
|
+
async def list_models(kind: str | None = None) -> str:
|
|
206
|
+
"""List available generation models with their ids, aspect/voice choices, options and credit prices.
|
|
207
|
+
|
|
208
|
+
kind: optional filter - "image", "video" or "audio".
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
cat = await _catalog()
|
|
212
|
+
rate = cat.get("usd_per_credit")
|
|
213
|
+
lines = [f"{BRAND} models (1 credit ≈ ${rate})" if rate else f"{BRAND} models"]
|
|
214
|
+
for m in cat["models"]:
|
|
215
|
+
if kind and m["kind"] != kind.lower():
|
|
216
|
+
continue
|
|
217
|
+
prices = ", ".join(f"{o}={c}cr" for o, c in m["prices"].items())
|
|
218
|
+
bits = [f"- {m['id']} [{m['kind']}] {m['name']}",
|
|
219
|
+
f" {m.get('aspect_label', 'Aspect')}: {', '.join(m['aspects'])}",
|
|
220
|
+
f" {m.get('option_label', 'Option')}: {prices}"]
|
|
221
|
+
extra = []
|
|
222
|
+
if m.get("max_refs"):
|
|
223
|
+
extra.append(f"refs≤{m['max_refs']}")
|
|
224
|
+
if m.get("needs_ref"):
|
|
225
|
+
extra.append("needs an input image")
|
|
226
|
+
if m.get("lyrics"):
|
|
227
|
+
extra.append(f"lyrics for: {', '.join(m['lyrics'])}")
|
|
228
|
+
if not m.get("available", True):
|
|
229
|
+
extra.append("UNAVAILABLE now")
|
|
230
|
+
if extra:
|
|
231
|
+
bits.append(" " + " · ".join(extra))
|
|
232
|
+
if m.get("tagline"):
|
|
233
|
+
bits.append(f" {m['tagline']}")
|
|
234
|
+
lines.append("\n".join(bits))
|
|
235
|
+
return "\n".join(lines)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
return _err(e)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@mcp.tool()
|
|
241
|
+
async def generate_image(prompt: str, model: str = "nano-banana-2", aspect: str = "1:1",
|
|
242
|
+
option: str | None = None, reference_images: list[str] | None = None) -> list:
|
|
243
|
+
"""Generate an image and save it locally. Returns the file path, cost and remaining credits (plus a preview).
|
|
244
|
+
|
|
245
|
+
prompt: what to draw.
|
|
246
|
+
model: image model id (see list_models(kind="image")), default nano-banana-2.
|
|
247
|
+
aspect: aspect ratio, e.g. "1:1", "16:9", "9:16".
|
|
248
|
+
option: resolution/quality option such as "1K", "2K", "4K"; defaults to the model's first (cheapest) option.
|
|
249
|
+
reference_images: optional list of local PNG/JPEG/WEBP paths used as references.
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
m = await _model(model, "image")
|
|
253
|
+
if m.get("needs_ref"):
|
|
254
|
+
return [f"Error: {model} is a tool, not a generator. Use upscale_image for it."]
|
|
255
|
+
refs = reference_images or []
|
|
256
|
+
if len(refs) > m.get("max_refs", 0):
|
|
257
|
+
return [f"Error: {m['name']} accepts up to {m.get('max_refs', 0)} reference images"]
|
|
258
|
+
fields = {"model": model, "prompt": prompt, "aspect": _pick(aspect, m["aspects"], "Aspect", model),
|
|
259
|
+
"option": _pick(option, list(m["prices"]), m.get("option_label", "Option"), model)}
|
|
260
|
+
return _with_image(await _run_job(fields, refs, prompt))
|
|
261
|
+
except Exception as e:
|
|
262
|
+
return [_err(e)]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@mcp.tool()
|
|
266
|
+
async def generate_video(prompt: str, model: str = "veo-3.1-fast", aspect: str = "16:9",
|
|
267
|
+
option: str = "8s", image_path: str | None = None) -> str:
|
|
268
|
+
"""Generate a video clip and save it locally. Returns the file path, cost and remaining credits.
|
|
269
|
+
|
|
270
|
+
prompt: the shot - subject, action, camera move, light.
|
|
271
|
+
model: video model id (see list_models(kind="video")), default veo-3.1-fast.
|
|
272
|
+
aspect: "16:9", "9:16" (some models also "1:1" etc.).
|
|
273
|
+
option: duration such as "4s", "6s", "8s"; must be one the model offers.
|
|
274
|
+
image_path: optional local image for image-to-video (first frame / reference).
|
|
275
|
+
Video takes 1-5 minutes; the call waits up to 10 minutes.
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
m = await _model(model, "video")
|
|
279
|
+
refs = [image_path] if image_path else []
|
|
280
|
+
if refs and not m.get("max_refs"):
|
|
281
|
+
return f"Error: {m['name']} does not accept an input image"
|
|
282
|
+
opts = list(m["prices"])
|
|
283
|
+
if option == "8s" and option not in opts: # default doesn't fit this model -> its first duration
|
|
284
|
+
option = None
|
|
285
|
+
fields = {"model": model, "prompt": prompt, "aspect": _pick(aspect, m["aspects"], "Aspect", model),
|
|
286
|
+
"option": _pick(option, opts, "Duration", model)}
|
|
287
|
+
return _summary(await _run_job(fields, refs, prompt))
|
|
288
|
+
except Exception as e:
|
|
289
|
+
return _err(e)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@mcp.tool()
|
|
293
|
+
async def generate_audio(text_or_prompt: str, model: str = "voice", aspect: str | None = None,
|
|
294
|
+
option: str | None = None, lyrics: str | None = None) -> str:
|
|
295
|
+
"""Generate audio (voiceover, background music, a song or a sound effect) and save it locally.
|
|
296
|
+
|
|
297
|
+
text_or_prompt: for "voice" the text to speak; for "music"/"song"/"sfx" a description of the sound.
|
|
298
|
+
model: "voice" (text to speech), "music" (instrumental background), "song" (sung, needs lyrics) or "sfx".
|
|
299
|
+
aspect: the voice for "voice" (e.g. "Deep narrator", "Warm female"), the mood for "music" (e.g. "Cinematic",
|
|
300
|
+
"Lo-fi"), the vocal for "song" ("Female vocal", "Male vocal", "Duet"); defaults to the first choice.
|
|
301
|
+
See list_models(kind="audio").
|
|
302
|
+
option: length/duration, e.g. "300 chars" for voice, "30s" for music, "60s" for song, "5s" for sfx; defaults to the first.
|
|
303
|
+
lyrics: song lyrics, required for "song".
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
m = await _model(model, "audio")
|
|
307
|
+
fields = {"model": model, "prompt": text_or_prompt,
|
|
308
|
+
"aspect": _pick(aspect, m["aspects"], m.get("aspect_label", "Aspect"), model),
|
|
309
|
+
"option": _pick(option, list(m["prices"]), m.get("option_label", "Option"), model)}
|
|
310
|
+
if fields["aspect"] in (m.get("lyrics") or []):
|
|
311
|
+
if not lyrics:
|
|
312
|
+
return f"Error: '{fields['aspect']}' needs lyrics. Pass lyrics or use aspect='Instrumental'."
|
|
313
|
+
fields["lyrics"] = lyrics
|
|
314
|
+
return _summary(await _run_job(fields, [], text_or_prompt))
|
|
315
|
+
except Exception as e:
|
|
316
|
+
return _err(e)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@mcp.tool()
|
|
320
|
+
async def upscale_image(path: str, scale: str = "2x") -> list:
|
|
321
|
+
"""Upscale a local image 2x or 4x and save the result next to other outputs.
|
|
322
|
+
|
|
323
|
+
path: local PNG/JPEG/WEBP file.
|
|
324
|
+
scale: "2x" or "4x".
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
m = await _model("upscale", "image")
|
|
328
|
+
fields = {"model": "upscale", "prompt": "", "aspect": m["aspects"][0],
|
|
329
|
+
"option": _pick(scale, list(m["prices"]), "Scale", "upscale")}
|
|
330
|
+
return _with_image(await _run_job(fields, [path], Path(path).stem + "-" + fields["option"]))
|
|
331
|
+
except Exception as e:
|
|
332
|
+
return [_err(e)]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@mcp.tool()
|
|
336
|
+
async def get_balance() -> str:
|
|
337
|
+
"""Show the remaining credit balance of the account behind TURBOGEN_API_KEY."""
|
|
338
|
+
try:
|
|
339
|
+
async with _client() as c:
|
|
340
|
+
r = await c.get("/api/jobs", params={"limit": 1})
|
|
341
|
+
_raise_for(r)
|
|
342
|
+
credits = r.json().get("credits")
|
|
343
|
+
rate = (await _catalog()).get("usd_per_credit")
|
|
344
|
+
usd = f" (≈ ${credits * rate:.2f})" if rate and credits is not None else ""
|
|
345
|
+
return f"Credits: {credits}{usd}. Top up at {_base()}/#/pricing"
|
|
346
|
+
except Exception as e:
|
|
347
|
+
return _err(e)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@mcp.tool()
|
|
351
|
+
async def job_status(job_id: str, download: bool = False) -> str:
|
|
352
|
+
"""Check a generation job by id (e.g. one that outlived the 10-minute wait).
|
|
353
|
+
|
|
354
|
+
download: if true and the job is done, save its file to the output folder and return the path.
|
|
355
|
+
"""
|
|
356
|
+
try:
|
|
357
|
+
async with _client() as c:
|
|
358
|
+
job, credits = await _find_job(c, job_id)
|
|
359
|
+
if not job:
|
|
360
|
+
return f"Error: job {job_id} not found among your 200 most recent jobs"
|
|
361
|
+
lines = [f"Job {job['id']}: {job['status']}", f"Model: {job['model']} · Cost: {job.get('cost')} credits",
|
|
362
|
+
f"Created: {job.get('created_at')}"]
|
|
363
|
+
if job.get("note"):
|
|
364
|
+
lines.append(f"Note: {job['note']}")
|
|
365
|
+
if job["status"] == "done" and download:
|
|
366
|
+
lines.append(f"Saved: {await _download(c, job, job.get('prompt') or 'result')}")
|
|
367
|
+
lines.append(f"Credits left: {credits}")
|
|
368
|
+
return "\n".join(lines)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
return _err(e)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def main() -> None:
|
|
374
|
+
mcp.run() # stdio transport
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
main()
|