webvoice-mcp 0.2.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.
- webvoice_mcp-0.2.0/LICENSE +21 -0
- webvoice_mcp-0.2.0/PKG-INFO +134 -0
- webvoice_mcp-0.2.0/README.md +226 -0
- webvoice_mcp-0.2.0/pyproject.toml +42 -0
- webvoice_mcp-0.2.0/setup.cfg +4 -0
- webvoice_mcp-0.2.0/tests/test_accounts_utils.py +61 -0
- webvoice_mcp-0.2.0/tests/test_agent_registration.py +155 -0
- webvoice_mcp-0.2.0/tests/test_billing_credits.py +61 -0
- webvoice_mcp-0.2.0/tests/test_chat_tools.py +110 -0
- webvoice_mcp-0.2.0/tests/test_minimax_official_tts.py +76 -0
- webvoice_mcp-0.2.0/tests/test_solana_deposits.py +95 -0
- webvoice_mcp-0.2.0/tests/test_tts_preview.py +32 -0
- webvoice_mcp-0.2.0/tests/test_urls_smoke.py +26 -0
- webvoice_mcp-0.2.0/tests/test_webvoice_mcp.py +127 -0
- webvoice_mcp-0.2.0/webvoice_mcp/README.md +108 -0
- webvoice_mcp-0.2.0/webvoice_mcp/__init__.py +3 -0
- webvoice_mcp-0.2.0/webvoice_mcp/__main__.py +4 -0
- webvoice_mcp-0.2.0/webvoice_mcp/client.py +300 -0
- webvoice_mcp-0.2.0/webvoice_mcp/server.py +301 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/PKG-INFO +134 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/SOURCES.txt +23 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/dependency_links.txt +1 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/entry_points.txt +2 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/requires.txt +2 -0
- webvoice_mcp-0.2.0/webvoice_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EasyTaskFlow / WebVoice
|
|
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,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webvoice-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MCP server for WebVoice — agent registration, chat, TTS, STT, translation, images
|
|
5
|
+
Author-email: EasyTaskFlow / WebVoice <service@easytaskflow.app>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://webvoice.easytaskflow.app
|
|
8
|
+
Project-URL: Documentation, https://webvoice.easytaskflow.app/api/documentation/#mcp
|
|
9
|
+
Project-URL: Repository, https://github.com/easytaskflow/webvoice-mcp
|
|
10
|
+
Project-URL: Issues, https://github.com/easytaskflow/webvoice-mcp/issues
|
|
11
|
+
Keywords: mcp,webvoice,tts,stt,cursor,agents,solana
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mcp>=1.6.0
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# WebVoice MCP Server
|
|
28
|
+
|
|
29
|
+
<!-- mcp-name: io.github.easytaskflow/webvoice-mcp -->
|
|
30
|
+
|
|
31
|
+
Local [Model Context Protocol](https://modelcontextprotocol.io) server that exposes WebVoice REST API tools to **Cursor**, Claude Desktop, and other MCP clients.
|
|
32
|
+
|
|
33
|
+
**Install from PyPI:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install webvoice-mcp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### From source (development)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -r requirements-mcp.txt
|
|
43
|
+
# or editable install from repo root:
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configure Cursor
|
|
48
|
+
|
|
49
|
+
### Option A — Agent registration via MCP (no browser)
|
|
50
|
+
|
|
51
|
+
1. Add MCP **without** `WEBVOICE_API_KEY` first (or use the register tools from any client).
|
|
52
|
+
2. Call **`webvoice_register_send_code`** with your email.
|
|
53
|
+
3. Read the OTP from email, then **`webvoice_register_verify`** with the code.
|
|
54
|
+
4. Response includes **`api_key`** (once), **`onboarding.credits`**, **`onboarding.can_use_api`**, and optional **`onboarding.solana`** (wallet + `memo_code` for USDC/SOL top-up).
|
|
55
|
+
5. If `can_use_api` is true, use chat/TTS/STT immediately with welcome credits.
|
|
56
|
+
6. Set `WEBVOICE_API_KEY` in MCP config and restart Cursor (optional Solana/PayPal top-up later).
|
|
57
|
+
|
|
58
|
+
REST equivalent: `POST /api/v1/auth/send-code/` → `POST /api/v1/auth/verify-code/` with `create_api_key: true`. See [API docs](https://webvoice.easytaskflow.app/api/documentation/#agent-registration).
|
|
59
|
+
|
|
60
|
+
### Option B — Browser signup (human)
|
|
61
|
+
|
|
62
|
+
1. **Login** — email OTP or Google (`/accounts/login/`). New users get welcome + daily free credits.
|
|
63
|
+
2. **API key** — [API dashboard](https://webvoice.easytaskflow.app/api/) → Create key → copy `wv_…` (shown once).
|
|
64
|
+
3. **Credits (optional)** — [Buy credits](https://webvoice.easytaskflow.app/billing/purchase/), [Premium](https://webvoice.easytaskflow.app/billing/subscriptions/) (PayPal), or **Solana** (send USDC/SOL with your personal memo from `webvoice_onboarding`).
|
|
65
|
+
|
|
66
|
+
When balance is zero, MCP calls fail with insufficient credits; you receive an email with a recharge link.
|
|
67
|
+
|
|
68
|
+
### MCP config
|
|
69
|
+
|
|
70
|
+
Edit Cursor MCP config (`~/.cursor/mcp.json` or **Settings → MCP**):
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"webvoice": {
|
|
76
|
+
"command": "webvoice-mcp",
|
|
77
|
+
"env": {
|
|
78
|
+
"WEBVOICE_API_KEY": "wv_your_key_here"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If `webvoice-mcp` is not on PATH, use Python module form:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"webvoice": {
|
|
91
|
+
"command": "python",
|
|
92
|
+
"args": ["-m", "webvoice_mcp"],
|
|
93
|
+
"cwd": "/path/to/webvoice",
|
|
94
|
+
"env": {
|
|
95
|
+
"WEBVOICE_API_KEY": "wv_your_key_here"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Optional: `WEBVOICE_BASE_URL` (default `https://webvoice.easytaskflow.app/api/v1`).
|
|
103
|
+
|
|
104
|
+
## Tools
|
|
105
|
+
|
|
106
|
+
| Tool | Description |
|
|
107
|
+
|------|-------------|
|
|
108
|
+
| `webvoice_register_send_code` | Start registration — OTP to email |
|
|
109
|
+
| `webvoice_register_verify` | Complete registration → API key + onboarding (credits, can_use_api) |
|
|
110
|
+
| `webvoice_onboarding` | Credits, can_use_api, optional Solana wallet/memo, recharge URLs |
|
|
111
|
+
| `webvoice_status` | Credits balance |
|
|
112
|
+
| `webvoice_list_chat_models` | Available chat models |
|
|
113
|
+
| `webvoice_list_voices` | TTS voices |
|
|
114
|
+
| `webvoice_chat` | Chat completions (DeepSeek default) |
|
|
115
|
+
| `webvoice_tts` | Text-to-speech → MP3 |
|
|
116
|
+
| `webvoice_stt` | Transcribe local audio file |
|
|
117
|
+
| `webvoice_translate` | Text translation |
|
|
118
|
+
| `webvoice_image` | MiniMax image generation |
|
|
119
|
+
|
|
120
|
+
## Example agent flow
|
|
121
|
+
|
|
122
|
+
**New agent (register → use → optional top-up):**
|
|
123
|
+
|
|
124
|
+
1. `webvoice_register_send_code` → `webvoice_register_verify` → save `api_key`.
|
|
125
|
+
2. If `onboarding.can_use_api`: call `webvoice_chat` / `webvoice_tts` / … immediately.
|
|
126
|
+
3. Optional: `webvoice_onboarding` → Solana memo or PayPal URLs when you need more credits.
|
|
127
|
+
|
|
128
|
+
**Existing account:**
|
|
129
|
+
|
|
130
|
+
1. Ask the model to call `webvoice_chat` with your question.
|
|
131
|
+
2. Call `webvoice_tts` with `output_path` to save spoken reply.
|
|
132
|
+
3. Call `webvoice_stt` with a recorded `audio_path` for voice input.
|
|
133
|
+
|
|
134
|
+
Credits are billed on your WebVoice account per API call.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Kokoro VoiceITA - App Python per Generazione Voce Locale
|
|
2
|
+
|
|
3
|
+
App Python per generare voce usando **Kokoro TTS** completamente in locale, con supporto per la voce "Sara".
|
|
4
|
+
|
|
5
|
+
## 🎯 Caratteristiche
|
|
6
|
+
|
|
7
|
+
- ✅ Generazione voce completamente locale (nessun invio dati a server esterni)
|
|
8
|
+
- ✅ Supporto per la voce "Sara" e altre voci Kokoro
|
|
9
|
+
- ✅ Controllo velocità e formato audio
|
|
10
|
+
- ✅ API semplice e intuitiva
|
|
11
|
+
- ✅ Gestione automatica delle voci disponibili
|
|
12
|
+
|
|
13
|
+
## 📋 Prerequisiti
|
|
14
|
+
|
|
15
|
+
1. **Python 3.9 - 3.12** (⚠️ Python 3.13+ non è supportato da kokoro-tts)
|
|
16
|
+
- Se hai Python 3.13+, usa un ambiente virtuale con Python 3.12 o Docker
|
|
17
|
+
- Vedi [README_PYTHON.md](README_PYTHON.md) per dettagli
|
|
18
|
+
2. **Server Kokoro TTS locale** - Vedi [COMANDI_SERVER.md](COMANDI_SERVER.md) per dettagli completi
|
|
19
|
+
|
|
20
|
+
### 🚀 Avvio Rapido del Server
|
|
21
|
+
|
|
22
|
+
**Metodo più semplice:** Usa lo script automatico incluso:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python start_server.py
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Lo script proverà automaticamente diversi metodi per avviare il server.
|
|
29
|
+
|
|
30
|
+
### Opzioni Manuali
|
|
31
|
+
|
|
32
|
+
**Opzione 1: Docker (Consigliato)**
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Clona il repository Kokoro-FastAPI
|
|
36
|
+
git clone https://github.com/remsky/Kokoro-FastAPI.git
|
|
37
|
+
cd Kokoro-FastAPI
|
|
38
|
+
|
|
39
|
+
# Per CPU
|
|
40
|
+
cd docker/cpu
|
|
41
|
+
docker compose up --build
|
|
42
|
+
|
|
43
|
+
# Oppure per GPU (se disponibile)
|
|
44
|
+
cd docker/gpu
|
|
45
|
+
docker compose up --build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Il server sarà disponibile su `http://localhost:8880`
|
|
49
|
+
|
|
50
|
+
**Opzione 2: Server Locale Incluso (FastAPI)**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Installa dipendenze server
|
|
54
|
+
pip install -r requirements-server.txt
|
|
55
|
+
|
|
56
|
+
# Installa backend TTS (scegli uno):
|
|
57
|
+
pip install kokoro-tts # Richiede Python 3.9-3.12
|
|
58
|
+
# oppure
|
|
59
|
+
pip install pykokoro
|
|
60
|
+
|
|
61
|
+
# Avvia il server incluso
|
|
62
|
+
python kokoro_server.py
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Opzione 3: uvicorn (se hai Kokoro-FastAPI come modulo)**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install kokoro-fastapi
|
|
69
|
+
uvicorn kokoro_fastapi.app:app --host 0.0.0.0 --port 8880
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
📖 **Per maggiori dettagli, vedi [COMANDI_SERVER.md](COMANDI_SERVER.md)**
|
|
73
|
+
|
|
74
|
+
## 🚀 Installazione
|
|
75
|
+
|
|
76
|
+
1. **Clona o scarica questo progetto**
|
|
77
|
+
|
|
78
|
+
2. **Installa le dipendenze del CLIENT Python:**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Per usare il client (sempre necessario)
|
|
82
|
+
pip install -r requirements.txt
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. **Installa il SERVER Kokoro TTS (scegli un'opzione):**
|
|
86
|
+
|
|
87
|
+
**Opzione A: Docker (Consigliato - Nessuna installazione Python)**
|
|
88
|
+
```bash
|
|
89
|
+
# Vedi COMANDI_SERVER.md per dettagli
|
|
90
|
+
git clone https://github.com/remsky/Kokoro-FastAPI.git
|
|
91
|
+
cd Kokoro-FastAPI/docker/cpu
|
|
92
|
+
docker compose up --build
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Opzione B: Python diretto**
|
|
96
|
+
```bash
|
|
97
|
+
# Installa il server Kokoro
|
|
98
|
+
pip install -r requirements-server.txt
|
|
99
|
+
|
|
100
|
+
# Oppure direttamente:
|
|
101
|
+
pip install kokoro-tts-cli
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Nota:** `requirements.txt` contiene solo le dipendenze del **client**. Il **server** è separato e può essere avviato con Docker (più semplice) o installato con `requirements-server.txt`.
|
|
105
|
+
|
|
106
|
+
## 💻 Utilizzo
|
|
107
|
+
|
|
108
|
+
### Utilizzo Base
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from kokoro_tts_client import KokoroTTSClient
|
|
112
|
+
|
|
113
|
+
# Crea il client
|
|
114
|
+
client = KokoroTTSClient()
|
|
115
|
+
|
|
116
|
+
# Genera audio con la voce Sara
|
|
117
|
+
client.generate_speech_file(
|
|
118
|
+
text="Ciao! Sono Sara. Questo è un test.",
|
|
119
|
+
output_file="output/sara_test.wav",
|
|
120
|
+
voice="sara", # Cerca automaticamente la voce Sara
|
|
121
|
+
speed=1.0
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Esempio Completo
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from kokoro_tts_client import KokoroTTSClient
|
|
129
|
+
|
|
130
|
+
# Inizializza il client
|
|
131
|
+
client = KokoroTTSClient(base_url="http://localhost:8880")
|
|
132
|
+
|
|
133
|
+
# Verifica connessione
|
|
134
|
+
if client.check_connection():
|
|
135
|
+
print("Server raggiungibile!")
|
|
136
|
+
|
|
137
|
+
# Mostra voci disponibili
|
|
138
|
+
voices = client.get_available_voices()
|
|
139
|
+
print(f"Voci disponibili: {voices}")
|
|
140
|
+
|
|
141
|
+
# Genera audio
|
|
142
|
+
audio_data = client.generate_speech(
|
|
143
|
+
text="Questo è un esempio di sintesi vocale.",
|
|
144
|
+
voice="af_sarah", # o "sara" per ricerca automatica
|
|
145
|
+
output_file="output/esempio.wav",
|
|
146
|
+
speed=1.0,
|
|
147
|
+
response_format="wav"
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Eseguire l'Esempio
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python kokoro_tts_client.py
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Questo eseguirà un esempio completo che:
|
|
158
|
+
- Verifica la connessione al server
|
|
159
|
+
- Mostra le voci disponibili
|
|
160
|
+
- Cerca la voce "Sara"
|
|
161
|
+
- Genera un audio di esempio
|
|
162
|
+
|
|
163
|
+
## 🎤 Voci Disponibili
|
|
164
|
+
|
|
165
|
+
Kokoro supporta molte voci con prefissi:
|
|
166
|
+
- `af_*` - Voci femminili (es. `af_sarah`, `af_bella`, `af_sky`)
|
|
167
|
+
- `am_*` - Voci maschili (es. `am_adam`)
|
|
168
|
+
|
|
169
|
+
La funzione `find_voice()` cerca automaticamente la voce "Sara" tra quelle disponibili.
|
|
170
|
+
|
|
171
|
+
## 📝 Parametri
|
|
172
|
+
|
|
173
|
+
### `generate_speech()`
|
|
174
|
+
|
|
175
|
+
- `text` (str): Testo da convertire in voce
|
|
176
|
+
- `voice` (str): Nome della voce (default: "af_sarah")
|
|
177
|
+
- `output_file` (str, opzionale): Percorso del file di output
|
|
178
|
+
- `speed` (float): Velocità di riproduzione (default: 1.0, range consigliato: 0.5-2.0)
|
|
179
|
+
- `response_format` (str): Formato audio - "wav", "mp3", "opus" (default: "wav")
|
|
180
|
+
|
|
181
|
+
## 📁 Struttura Progetto
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
kokoro_voiceita/
|
|
185
|
+
├── kokoro_tts_client.py # Cliente principale
|
|
186
|
+
├── start_server.py # Script per avviare il server
|
|
187
|
+
├── example_usage.py # Esempi di utilizzo
|
|
188
|
+
├── config.py # File di configurazione
|
|
189
|
+
├── requirements.txt # Dipendenze Python
|
|
190
|
+
├── README.md # Questa documentazione
|
|
191
|
+
├── COMANDI_SERVER.md # Guida dettagliata per avviare il server
|
|
192
|
+
└── output/ # Directory per file audio generati (creata automaticamente)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 🔧 Risoluzione Problemi
|
|
196
|
+
|
|
197
|
+
### Errore: "Impossibile connettersi al server"
|
|
198
|
+
|
|
199
|
+
1. Verifica che il server Kokoro TTS sia avviato
|
|
200
|
+
2. Controlla che l'URL sia corretto (default: `http://localhost:8880`)
|
|
201
|
+
3. Verifica che la porta non sia bloccata da firewall
|
|
202
|
+
|
|
203
|
+
### Voce "Sara" non trovata
|
|
204
|
+
|
|
205
|
+
- La funzione cerca automaticamente varianti come `af_sarah`, `af_sara`
|
|
206
|
+
- Puoi specificare direttamente l'ID della voce (es. `af_sarah`)
|
|
207
|
+
- Usa `get_available_voices()` per vedere tutte le voci disponibili
|
|
208
|
+
|
|
209
|
+
### Formato MP3 non funziona
|
|
210
|
+
|
|
211
|
+
- Assicurati che FFmpeg sia installato sul sistema
|
|
212
|
+
- Usa "wav" come formato se hai problemi
|
|
213
|
+
|
|
214
|
+
## 📚 Risorse
|
|
215
|
+
|
|
216
|
+
- [Kokoro TTS](https://kokorotts.net/)
|
|
217
|
+
- [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI)
|
|
218
|
+
- [Documentazione Kokoro](https://docs.azerion.ai/)
|
|
219
|
+
|
|
220
|
+
## 📄 Licenza
|
|
221
|
+
|
|
222
|
+
Questo progetto è fornito come esempio. Verifica le licenze di Kokoro TTS per uso commerciale.
|
|
223
|
+
|
|
224
|
+
## 🤝 Contributi
|
|
225
|
+
|
|
226
|
+
Sentiti libero di aprire issue o pull request per miglioramenti!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "webvoice-mcp"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "MCP server for WebVoice — agent registration, chat, TTS, STT, translation, images"
|
|
9
|
+
readme = "webvoice_mcp/README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "EasyTaskFlow / WebVoice", email = "service@easytaskflow.app" }]
|
|
13
|
+
keywords = ["mcp", "webvoice", "tts", "stt", "cursor", "agents", "solana"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
]
|
|
24
|
+
license-files = ["LICENSE"]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"mcp>=1.6.0",
|
|
27
|
+
"httpx>=0.27.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
webvoice-mcp = "webvoice_mcp.server:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://webvoice.easytaskflow.app"
|
|
35
|
+
Documentation = "https://webvoice.easytaskflow.app/api/documentation/#mcp"
|
|
36
|
+
Repository = "https://github.com/easytaskflow/webvoice-mcp"
|
|
37
|
+
Issues = "https://github.com/easytaskflow/webvoice-mcp/issues"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["."]
|
|
41
|
+
include = ["webvoice_mcp*"]
|
|
42
|
+
exclude = ["tests*"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test per accounts.utils: cartelle media utente.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from django.contrib.auth.models import User
|
|
8
|
+
from django.test import TestCase, override_settings
|
|
9
|
+
|
|
10
|
+
from accounts.utils import (
|
|
11
|
+
USER_MEDIA_SUBDIRS,
|
|
12
|
+
audit_all_user_media_directories,
|
|
13
|
+
ensure_all_users_media_directories,
|
|
14
|
+
ensure_user_media_directories,
|
|
15
|
+
list_missing_media_subdirs_for_user,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EnsureUserMediaDirectoriesTests(TestCase):
|
|
20
|
+
def setUp(self):
|
|
21
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
22
|
+
self.addCleanup(self.tmp.cleanup)
|
|
23
|
+
|
|
24
|
+
@override_settings(MEDIA_ROOT=None)
|
|
25
|
+
def test_ensure_skips_without_media_root(self):
|
|
26
|
+
user = User.objects.create_user(username="u1", email="u1@example.com", password="x")
|
|
27
|
+
ensure_user_media_directories(user)
|
|
28
|
+
# Nessun crash
|
|
29
|
+
|
|
30
|
+
def test_ensure_creates_all_subdirs(self):
|
|
31
|
+
with self.settings(MEDIA_ROOT=self.tmp.name):
|
|
32
|
+
user = User.objects.create_user(username="u2", email="u2@example.com", password="x")
|
|
33
|
+
ensure_user_media_directories(user)
|
|
34
|
+
uid = str(user.pk)
|
|
35
|
+
for sub in USER_MEDIA_SUBDIRS:
|
|
36
|
+
path = os.path.join(self.tmp.name, sub, uid)
|
|
37
|
+
self.assertTrue(os.path.isdir(path), msg=path)
|
|
38
|
+
|
|
39
|
+
def test_list_missing_empty_after_ensure(self):
|
|
40
|
+
with self.settings(MEDIA_ROOT=self.tmp.name):
|
|
41
|
+
user = User.objects.create_user(username="u3", email="u3@example.com", password="x")
|
|
42
|
+
ensure_user_media_directories(user)
|
|
43
|
+
self.assertEqual(list_missing_media_subdirs_for_user(user), [])
|
|
44
|
+
|
|
45
|
+
def test_list_missing_when_no_media_root_lists_all_subdir_names(self):
|
|
46
|
+
with self.settings(MEDIA_ROOT=None):
|
|
47
|
+
user = User.objects.create_user(username="u4", email="u4@example.com", password="x")
|
|
48
|
+
missing = list_missing_media_subdirs_for_user(user)
|
|
49
|
+
self.assertEqual(set(missing), set(USER_MEDIA_SUBDIRS))
|
|
50
|
+
|
|
51
|
+
def test_audit_and_ensure_all(self):
|
|
52
|
+
with self.settings(MEDIA_ROOT=self.tmp.name):
|
|
53
|
+
User.objects.create_user(username="a", email="a@example.com", password="x")
|
|
54
|
+
User.objects.create_user(username="b", email="b@example.com", password="x")
|
|
55
|
+
r = ensure_all_users_media_directories()
|
|
56
|
+
self.assertEqual(r["users_processed"], 2)
|
|
57
|
+
audit = audit_all_user_media_directories()
|
|
58
|
+
self.assertTrue(audit["configured"])
|
|
59
|
+
self.assertEqual(audit["total_users"], 2)
|
|
60
|
+
self.assertEqual(audit["users_with_gaps"], 0)
|
|
61
|
+
self.assertEqual(audit["users_complete"], 2)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Tests for agent registration API (stateless email OTP + API key)."""
|
|
2
|
+
import json
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
from django.contrib.auth.models import User
|
|
7
|
+
from django.test import Client, TestCase, override_settings
|
|
8
|
+
|
|
9
|
+
from accounts.models import OTPCode
|
|
10
|
+
from api.models import APIKey
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@override_settings(
|
|
14
|
+
SOLANA_DEPOSIT_ENABLED=True,
|
|
15
|
+
SOLANA_DEPOSIT_WALLET="DepositWallet1111111111111111111111111111",
|
|
16
|
+
)
|
|
17
|
+
class AgentRegistrationAPITests(TestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.client = Client()
|
|
20
|
+
self.json_headers = {'HTTP_ACCEPT': 'application/json'}
|
|
21
|
+
|
|
22
|
+
def _post_json(self, url, payload):
|
|
23
|
+
return self.client.post(
|
|
24
|
+
url,
|
|
25
|
+
data=json.dumps(payload),
|
|
26
|
+
content_type='application/json',
|
|
27
|
+
**self.json_headers,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@patch('accounts.views.send_otp_email', return_value=True)
|
|
31
|
+
def test_send_code_json(self, _mock_mail):
|
|
32
|
+
resp = self._post_json(
|
|
33
|
+
'/api/v1/auth/send-code/',
|
|
34
|
+
{
|
|
35
|
+
'email': 'newagent@test.com',
|
|
36
|
+
'accept_privacy': True,
|
|
37
|
+
'accept_terms': True,
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
self.assertEqual(resp.status_code, 200)
|
|
41
|
+
data = resp.json()
|
|
42
|
+
self.assertTrue(data['success'])
|
|
43
|
+
self.assertTrue(data['is_new_user'])
|
|
44
|
+
|
|
45
|
+
@patch('accounts.views.send_otp_email', return_value=True)
|
|
46
|
+
def test_verify_creates_user_and_api_key_stateless(self, _mock_mail):
|
|
47
|
+
email = 'agent2@test.com'
|
|
48
|
+
self._post_json(
|
|
49
|
+
'/api/v1/auth/send-code/',
|
|
50
|
+
{'email': email, 'accept_privacy': True, 'accept_terms': True},
|
|
51
|
+
)
|
|
52
|
+
otp = OTPCode.objects.filter(email=email, used=False).latest('created_at')
|
|
53
|
+
|
|
54
|
+
resp = self._post_json(
|
|
55
|
+
'/api/v1/auth/verify-code/',
|
|
56
|
+
{
|
|
57
|
+
'email': email,
|
|
58
|
+
'code': otp.code,
|
|
59
|
+
'accept_privacy': True,
|
|
60
|
+
'accept_terms': True,
|
|
61
|
+
'create_api_key': True,
|
|
62
|
+
'api_key_name': 'test-agent',
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
self.assertEqual(resp.status_code, 200)
|
|
66
|
+
data = resp.json()
|
|
67
|
+
self.assertTrue(data['success'])
|
|
68
|
+
self.assertTrue(data['is_new_user'])
|
|
69
|
+
self.assertIn('api_key', data)
|
|
70
|
+
self.assertTrue(data['api_key'].startswith('wv_'))
|
|
71
|
+
self.assertIn('onboarding', data)
|
|
72
|
+
self.assertIn('solana', data['onboarding'])
|
|
73
|
+
self.assertTrue(data['onboarding']['solana']['available'])
|
|
74
|
+
self.assertIn('memo_code', data['onboarding']['solana'])
|
|
75
|
+
self.assertTrue(data['onboarding']['can_use_api'])
|
|
76
|
+
|
|
77
|
+
user = User.objects.get(email=email)
|
|
78
|
+
self.assertGreaterEqual(user.profile.credits, Decimal('20'))
|
|
79
|
+
self.assertEqual(APIKey.objects.filter(user=user).count(), 1)
|
|
80
|
+
|
|
81
|
+
def test_onboarding_requires_api_key(self):
|
|
82
|
+
resp = self.client.get(
|
|
83
|
+
'/api/v1/onboarding/',
|
|
84
|
+
HTTP_ACCEPT='application/json',
|
|
85
|
+
)
|
|
86
|
+
self.assertEqual(resp.status_code, 401)
|
|
87
|
+
|
|
88
|
+
@patch('accounts.views.send_otp_email', return_value=True)
|
|
89
|
+
def test_onboarding_with_api_key(self, _mock_mail):
|
|
90
|
+
email = 'agent3@test.com'
|
|
91
|
+
self._post_json(
|
|
92
|
+
'/api/v1/auth/send-code/',
|
|
93
|
+
{'email': email, 'accept_privacy': True, 'accept_terms': True},
|
|
94
|
+
)
|
|
95
|
+
otp = OTPCode.objects.filter(email=email, used=False).latest('created_at')
|
|
96
|
+
verify = self._post_json(
|
|
97
|
+
'/api/v1/auth/verify-code/',
|
|
98
|
+
{'email': email, 'code': otp.code, 'create_api_key': True},
|
|
99
|
+
).json()
|
|
100
|
+
api_key = verify['api_key']
|
|
101
|
+
|
|
102
|
+
resp = self.client.get(
|
|
103
|
+
'/api/v1/onboarding/',
|
|
104
|
+
HTTP_ACCEPT='application/json',
|
|
105
|
+
HTTP_X_API_KEY=api_key,
|
|
106
|
+
)
|
|
107
|
+
self.assertEqual(resp.status_code, 200)
|
|
108
|
+
data = resp.json()
|
|
109
|
+
self.assertTrue(data['success'])
|
|
110
|
+
self.assertIn('solana', data)
|
|
111
|
+
|
|
112
|
+
@patch('accounts.views.send_otp_email', return_value=True)
|
|
113
|
+
def test_verify_existing_user_not_marked_new_despite_session(self, _mock_mail):
|
|
114
|
+
"""Session is_new_user from another send-code must not flag returning users as new."""
|
|
115
|
+
email = 'returning@test.com'
|
|
116
|
+
User.objects.create_user(username='returning', email=email)
|
|
117
|
+
User.objects.get(email=email).set_unusable_password()
|
|
118
|
+
|
|
119
|
+
# Poison session: send-code for a different new email
|
|
120
|
+
self._post_json(
|
|
121
|
+
'/api/v1/auth/send-code/',
|
|
122
|
+
{'email': 'other-new@test.com', 'accept_privacy': True, 'accept_terms': True},
|
|
123
|
+
)
|
|
124
|
+
otp = OTPCode.create_code(email, '127.0.0.1', validity_minutes=15)
|
|
125
|
+
|
|
126
|
+
resp = self._post_json(
|
|
127
|
+
'/api/v1/auth/verify-code/',
|
|
128
|
+
{'email': email, 'code': otp.code, 'create_api_key': True},
|
|
129
|
+
)
|
|
130
|
+
self.assertEqual(resp.status_code, 200)
|
|
131
|
+
data = resp.json()
|
|
132
|
+
self.assertFalse(data['is_new_user'])
|
|
133
|
+
self.assertIn('api_key', data)
|
|
134
|
+
|
|
135
|
+
@patch('billing.solana_deposits.get_or_create_deposit_account')
|
|
136
|
+
@patch('accounts.views.send_otp_email', return_value=True)
|
|
137
|
+
def test_onboarding_survives_missing_solana_tables(self, _mock_mail, mock_memo):
|
|
138
|
+
from django.db.utils import ProgrammingError
|
|
139
|
+
|
|
140
|
+
mock_memo.side_effect = ProgrammingError("Table doesn't exist")
|
|
141
|
+
email = 'nosolana@test.com'
|
|
142
|
+
self._post_json(
|
|
143
|
+
'/api/v1/auth/send-code/',
|
|
144
|
+
{'email': email, 'accept_privacy': True, 'accept_terms': True},
|
|
145
|
+
)
|
|
146
|
+
otp = OTPCode.objects.filter(email=email, used=False).latest('created_at')
|
|
147
|
+
verify = self._post_json(
|
|
148
|
+
'/api/v1/auth/verify-code/',
|
|
149
|
+
{'email': email, 'code': otp.code, 'create_api_key': True},
|
|
150
|
+
)
|
|
151
|
+
self.assertEqual(verify.status_code, 200)
|
|
152
|
+
onboarding = verify.json()['onboarding']
|
|
153
|
+
self.assertIn('api_key', verify.json())
|
|
154
|
+
self.assertFalse(onboarding['solana']['available'])
|
|
155
|
+
self.assertTrue(onboarding['can_use_api'])
|