kwork-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.
- kwork_mcp-0.1.0/.env.example +11 -0
- kwork_mcp-0.1.0/.gitignore +13 -0
- kwork_mcp-0.1.0/LICENSE +21 -0
- kwork_mcp-0.1.0/PKG-INFO +213 -0
- kwork_mcp-0.1.0/README.md +188 -0
- kwork_mcp-0.1.0/assets/banner.svg +56 -0
- kwork_mcp-0.1.0/pyproject.toml +61 -0
- kwork_mcp-0.1.0/src/kwork_mcp/__init__.py +15 -0
- kwork_mcp-0.1.0/src/kwork_mcp/__main__.py +3 -0
- kwork_mcp-0.1.0/src/kwork_mcp/config.py +27 -0
- kwork_mcp-0.1.0/src/kwork_mcp/errors.py +49 -0
- kwork_mcp-0.1.0/src/kwork_mcp/rate_limiter.py +41 -0
- kwork_mcp-0.1.0/src/kwork_mcp/server.py +32 -0
- kwork_mcp-0.1.0/src/kwork_mcp/session.py +129 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/__init__.py +26 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/categories.py +53 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/dialogs.py +106 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/kworks.py +152 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/notifications.py +46 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/offers.py +149 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/orders.py +110 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/profile.py +70 -0
- kwork_mcp-0.1.0/src/kwork_mcp/tools/projects.py +158 -0
- kwork_mcp-0.1.0/tests/__init__.py +0 -0
- kwork_mcp-0.1.0/tests/test_config.py +36 -0
- kwork_mcp-0.1.0/tests/test_rate_limiter.py +40 -0
- kwork_mcp-0.1.0/tests/test_session.py +95 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Required: one of (LOGIN+PASSWORD) or TOKEN
|
|
2
|
+
KWORK_LOGIN=your_login
|
|
3
|
+
KWORK_PASSWORD=your_password
|
|
4
|
+
# KWORK_PHONE_LAST=1234 # Last 4 digits for 2FA (if enabled)
|
|
5
|
+
# KWORK_TOKEN=existing_token # Skip login, use this token directly
|
|
6
|
+
|
|
7
|
+
# Optional
|
|
8
|
+
# KWORK_PROXY_URL=socks5://host:port
|
|
9
|
+
# KWORK_RPS_LIMIT=2
|
|
10
|
+
# KWORK_BURST_LIMIT=5
|
|
11
|
+
# KWORK_TOKEN_FILE=~/.kwork_token
|
kwork_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 simonether
|
|
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.
|
kwork_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kwork-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for managing Kwork freelance marketplace
|
|
5
|
+
Project-URL: Homepage, https://github.com/simonether/kwork-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/simonether/kwork-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/simonether/kwork-mcp/issues
|
|
8
|
+
Author-email: Simon Sudarushkin <simonsudarushkin@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude,freelance,kwork,mcp,mcp-server
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: kwork>=0.2.0
|
|
21
|
+
Requires-Dist: loguru>=0.7
|
|
22
|
+
Requires-Dist: mcp>=1.20
|
|
23
|
+
Requires-Dist: pydantic-settings>=2.7
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="assets/banner.svg" alt="kwork-mcp" width="100%">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://github.com/astral-sh/ruff)
|
|
33
|
+
[](https://modelcontextprotocol.io)
|
|
34
|
+
|
|
35
|
+
MCP server that exposes 25 tools for the [Kwork](https://kwork.ru) freelance marketplace — browse projects, submit offers, manage orders, send messages, and more.
|
|
36
|
+
|
|
37
|
+
Built with [FastMCP](https://github.com/jlowin/fastmcp) (via [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)) and [pykwork](https://github.com/kesha1225/pykwork).
|
|
38
|
+
|
|
39
|
+
## 🚀 Setup
|
|
40
|
+
|
|
41
|
+
### Requirements
|
|
42
|
+
|
|
43
|
+
- Python 3.12+
|
|
44
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
45
|
+
- Kwork account (login/password or API token)
|
|
46
|
+
|
|
47
|
+
### Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/simonether/kwork-mcp.git
|
|
51
|
+
cd kwork-mcp
|
|
52
|
+
uv sync
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Configure
|
|
56
|
+
|
|
57
|
+
Copy `.env.example` to `.env` and fill in your credentials:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cp .env.example .env
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Variable | Required | Default | Description |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `KWORK_LOGIN` | yes* | — | Kwork login |
|
|
66
|
+
| `KWORK_PASSWORD` | yes* | — | Kwork password |
|
|
67
|
+
| `KWORK_TOKEN` | yes* | — | Auth token (skips login) |
|
|
68
|
+
| `KWORK_PHONE_LAST` | no | — | Last 4 digits of phone (2FA) |
|
|
69
|
+
| `KWORK_PROXY_URL` | no | — | SOCKS5 proxy |
|
|
70
|
+
| `KWORK_RPS_LIMIT` | no | `2` | Requests per second |
|
|
71
|
+
| `KWORK_BURST_LIMIT` | no | `5` | Burst limit |
|
|
72
|
+
| `KWORK_TOKEN_FILE` | no | `~/.kwork_token` | Token persistence path |
|
|
73
|
+
|
|
74
|
+
\*Either `KWORK_TOKEN` or both `KWORK_LOGIN` + `KWORK_PASSWORD`.
|
|
75
|
+
|
|
76
|
+
Auth priority: `KWORK_TOKEN` env → saved token file → fresh login.
|
|
77
|
+
|
|
78
|
+
## 💡 Usage
|
|
79
|
+
|
|
80
|
+
### Claude Desktop
|
|
81
|
+
|
|
82
|
+
Add to `claude_desktop_config.json`:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"kwork": {
|
|
88
|
+
"command": "uv",
|
|
89
|
+
"args": ["run", "--directory", "/path/to/kwork-mcp", "kwork-mcp"],
|
|
90
|
+
"env": {
|
|
91
|
+
"KWORK_LOGIN": "your_login",
|
|
92
|
+
"KWORK_PASSWORD": "your_password"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Claude Code
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
claude mcp add kwork -- uv run --directory /path/to/kwork-mcp kwork-mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### stdio
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv run kwork-mcp
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 🛠 Tools
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><strong>Profile</strong> (3 tools)</summary>
|
|
115
|
+
|
|
116
|
+
| Tool | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `get_me` | Current user profile, rating, balance |
|
|
119
|
+
| `get_connects` | Connect count for exchange offers |
|
|
120
|
+
| `get_user_info` | Public user info by ID |
|
|
121
|
+
|
|
122
|
+
</details>
|
|
123
|
+
|
|
124
|
+
<details>
|
|
125
|
+
<summary><strong>Projects</strong> (4 tools)</summary>
|
|
126
|
+
|
|
127
|
+
| Tool | Description |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `list_projects` | Browse exchange projects with filters |
|
|
130
|
+
| `get_project` | Project details by ID |
|
|
131
|
+
| `search_projects` | Search by text query |
|
|
132
|
+
| `get_exchange_info` | Exchange marketplace stats |
|
|
133
|
+
|
|
134
|
+
</details>
|
|
135
|
+
|
|
136
|
+
<details>
|
|
137
|
+
<summary><strong>Offers</strong> (4 tools)</summary>
|
|
138
|
+
|
|
139
|
+
| Tool | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `list_my_offers` | Your exchange offers |
|
|
142
|
+
| `get_offer` | Offer details by ID |
|
|
143
|
+
| `submit_offer` | Submit offer to a project (costs 1 connect) |
|
|
144
|
+
| `delete_offer` | Delete an offer |
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary><strong>Orders</strong> (3 tools)</summary>
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `list_worker_orders` | Seller orders (all statuses) |
|
|
154
|
+
| `get_order_details` | Order details by ID |
|
|
155
|
+
| `send_order_for_approval` | Submit work for buyer review |
|
|
156
|
+
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
<details>
|
|
160
|
+
<summary><strong>Dialogs</strong> (4 tools)</summary>
|
|
161
|
+
|
|
162
|
+
| Tool | Description |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `list_dialogs` | Conversations with latest messages |
|
|
165
|
+
| `get_dialog` | Messages by username |
|
|
166
|
+
| `send_message` | Send direct message |
|
|
167
|
+
| `mark_dialog_read` | Mark as read |
|
|
168
|
+
|
|
169
|
+
</details>
|
|
170
|
+
|
|
171
|
+
<details>
|
|
172
|
+
<summary><strong>Kworks</strong> (4 tools)</summary>
|
|
173
|
+
|
|
174
|
+
| Tool | Description |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `list_my_kworks` | Your services grouped by status |
|
|
177
|
+
| `get_kwork_details` | Kwork details by ID |
|
|
178
|
+
| `start_kwork` | Activate a paused kwork |
|
|
179
|
+
| `pause_kwork` | Pause an active kwork |
|
|
180
|
+
|
|
181
|
+
</details>
|
|
182
|
+
|
|
183
|
+
<details>
|
|
184
|
+
<summary><strong>Categories</strong> (2 tools)</summary>
|
|
185
|
+
|
|
186
|
+
| Tool | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `list_categories` | Full category tree |
|
|
189
|
+
| `get_favorite_categories` | User's favorite categories |
|
|
190
|
+
|
|
191
|
+
</details>
|
|
192
|
+
|
|
193
|
+
<details>
|
|
194
|
+
<summary><strong>Notifications</strong> (1 tool)</summary>
|
|
195
|
+
|
|
196
|
+
| Tool | Description |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `list_notifications` | User notifications |
|
|
199
|
+
|
|
200
|
+
</details>
|
|
201
|
+
|
|
202
|
+
## 🧑💻 Development
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
uv sync --dev
|
|
206
|
+
uv run python -m pytest tests/ -x -v
|
|
207
|
+
uv run ruff check .
|
|
208
|
+
uv run ruff format --check .
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/banner.svg" alt="kwork-mcp" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/astral-sh/ruff)
|
|
8
|
+
[](https://modelcontextprotocol.io)
|
|
9
|
+
|
|
10
|
+
MCP server that exposes 25 tools for the [Kwork](https://kwork.ru) freelance marketplace — browse projects, submit offers, manage orders, send messages, and more.
|
|
11
|
+
|
|
12
|
+
Built with [FastMCP](https://github.com/jlowin/fastmcp) (via [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)) and [pykwork](https://github.com/kesha1225/pykwork).
|
|
13
|
+
|
|
14
|
+
## 🚀 Setup
|
|
15
|
+
|
|
16
|
+
### Requirements
|
|
17
|
+
|
|
18
|
+
- Python 3.12+
|
|
19
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
20
|
+
- Kwork account (login/password or API token)
|
|
21
|
+
|
|
22
|
+
### Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/simonether/kwork-mcp.git
|
|
26
|
+
cd kwork-mcp
|
|
27
|
+
uv sync
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Configure
|
|
31
|
+
|
|
32
|
+
Copy `.env.example` to `.env` and fill in your credentials:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cp .env.example .env
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Variable | Required | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `KWORK_LOGIN` | yes* | — | Kwork login |
|
|
41
|
+
| `KWORK_PASSWORD` | yes* | — | Kwork password |
|
|
42
|
+
| `KWORK_TOKEN` | yes* | — | Auth token (skips login) |
|
|
43
|
+
| `KWORK_PHONE_LAST` | no | — | Last 4 digits of phone (2FA) |
|
|
44
|
+
| `KWORK_PROXY_URL` | no | — | SOCKS5 proxy |
|
|
45
|
+
| `KWORK_RPS_LIMIT` | no | `2` | Requests per second |
|
|
46
|
+
| `KWORK_BURST_LIMIT` | no | `5` | Burst limit |
|
|
47
|
+
| `KWORK_TOKEN_FILE` | no | `~/.kwork_token` | Token persistence path |
|
|
48
|
+
|
|
49
|
+
\*Either `KWORK_TOKEN` or both `KWORK_LOGIN` + `KWORK_PASSWORD`.
|
|
50
|
+
|
|
51
|
+
Auth priority: `KWORK_TOKEN` env → saved token file → fresh login.
|
|
52
|
+
|
|
53
|
+
## 💡 Usage
|
|
54
|
+
|
|
55
|
+
### Claude Desktop
|
|
56
|
+
|
|
57
|
+
Add to `claude_desktop_config.json`:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"kwork": {
|
|
63
|
+
"command": "uv",
|
|
64
|
+
"args": ["run", "--directory", "/path/to/kwork-mcp", "kwork-mcp"],
|
|
65
|
+
"env": {
|
|
66
|
+
"KWORK_LOGIN": "your_login",
|
|
67
|
+
"KWORK_PASSWORD": "your_password"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Claude Code
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
claude mcp add kwork -- uv run --directory /path/to/kwork-mcp kwork-mcp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### stdio
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv run kwork-mcp
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 🛠 Tools
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary><strong>Profile</strong> (3 tools)</summary>
|
|
90
|
+
|
|
91
|
+
| Tool | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `get_me` | Current user profile, rating, balance |
|
|
94
|
+
| `get_connects` | Connect count for exchange offers |
|
|
95
|
+
| `get_user_info` | Public user info by ID |
|
|
96
|
+
|
|
97
|
+
</details>
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><strong>Projects</strong> (4 tools)</summary>
|
|
101
|
+
|
|
102
|
+
| Tool | Description |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `list_projects` | Browse exchange projects with filters |
|
|
105
|
+
| `get_project` | Project details by ID |
|
|
106
|
+
| `search_projects` | Search by text query |
|
|
107
|
+
| `get_exchange_info` | Exchange marketplace stats |
|
|
108
|
+
|
|
109
|
+
</details>
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><strong>Offers</strong> (4 tools)</summary>
|
|
113
|
+
|
|
114
|
+
| Tool | Description |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `list_my_offers` | Your exchange offers |
|
|
117
|
+
| `get_offer` | Offer details by ID |
|
|
118
|
+
| `submit_offer` | Submit offer to a project (costs 1 connect) |
|
|
119
|
+
| `delete_offer` | Delete an offer |
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
<details>
|
|
124
|
+
<summary><strong>Orders</strong> (3 tools)</summary>
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `list_worker_orders` | Seller orders (all statuses) |
|
|
129
|
+
| `get_order_details` | Order details by ID |
|
|
130
|
+
| `send_order_for_approval` | Submit work for buyer review |
|
|
131
|
+
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
<details>
|
|
135
|
+
<summary><strong>Dialogs</strong> (4 tools)</summary>
|
|
136
|
+
|
|
137
|
+
| Tool | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `list_dialogs` | Conversations with latest messages |
|
|
140
|
+
| `get_dialog` | Messages by username |
|
|
141
|
+
| `send_message` | Send direct message |
|
|
142
|
+
| `mark_dialog_read` | Mark as read |
|
|
143
|
+
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
<details>
|
|
147
|
+
<summary><strong>Kworks</strong> (4 tools)</summary>
|
|
148
|
+
|
|
149
|
+
| Tool | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `list_my_kworks` | Your services grouped by status |
|
|
152
|
+
| `get_kwork_details` | Kwork details by ID |
|
|
153
|
+
| `start_kwork` | Activate a paused kwork |
|
|
154
|
+
| `pause_kwork` | Pause an active kwork |
|
|
155
|
+
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
<details>
|
|
159
|
+
<summary><strong>Categories</strong> (2 tools)</summary>
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `list_categories` | Full category tree |
|
|
164
|
+
| `get_favorite_categories` | User's favorite categories |
|
|
165
|
+
|
|
166
|
+
</details>
|
|
167
|
+
|
|
168
|
+
<details>
|
|
169
|
+
<summary><strong>Notifications</strong> (1 tool)</summary>
|
|
170
|
+
|
|
171
|
+
| Tool | Description |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `list_notifications` | User notifications |
|
|
174
|
+
|
|
175
|
+
</details>
|
|
176
|
+
|
|
177
|
+
## 🧑💻 Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
uv sync --dev
|
|
181
|
+
uv run python -m pytest tests/ -x -v
|
|
182
|
+
uv run ruff check .
|
|
183
|
+
uv run ruff format --check .
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<svg viewBox="0 0 800 240" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#0c0c0c"/>
|
|
5
|
+
<stop offset="50%" style="stop-color:#100d16"/>
|
|
6
|
+
<stop offset="100%" style="stop-color:#161022"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="ac" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
9
|
+
<stop offset="0%" style="stop-color:#e040fb"/>
|
|
10
|
+
<stop offset="100%" style="stop-color:#536dfe"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<radialGradient id="gl" cx="65%" cy="50%">
|
|
13
|
+
<stop offset="0%" style="stop-color:rgba(224,64,251,0.07)"/>
|
|
14
|
+
<stop offset="100%" style="stop-color:transparent"/>
|
|
15
|
+
</radialGradient>
|
|
16
|
+
<filter id="txtGlow" x="-20%" y="-20%" width="140%" height="140%">
|
|
17
|
+
<feGaussianBlur stdDeviation="8" result="blur"/>
|
|
18
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
19
|
+
</filter>
|
|
20
|
+
</defs>
|
|
21
|
+
|
|
22
|
+
<rect width="800" height="240" fill="url(#bg)"/>
|
|
23
|
+
<rect width="800" height="240" fill="url(#gl)"/>
|
|
24
|
+
|
|
25
|
+
<!-- Sparse constellation -->
|
|
26
|
+
<line x1="520" y1="45" x2="620" y2="75" stroke="rgba(224,64,251,0.12)" stroke-width="0.5"/>
|
|
27
|
+
<line x1="620" y1="75" x2="580" y2="150" stroke="rgba(83,109,254,0.1)" stroke-width="0.5"/>
|
|
28
|
+
<line x1="580" y1="150" x2="520" y2="45" stroke="rgba(124,77,255,0.08)" stroke-width="0.5"/>
|
|
29
|
+
<line x1="620" y1="75" x2="720" y2="55" stroke="rgba(224,64,251,0.1)" stroke-width="0.5"/>
|
|
30
|
+
<line x1="720" y1="55" x2="740" y2="140" stroke="rgba(83,109,254,0.09)" stroke-width="0.5"/>
|
|
31
|
+
<line x1="740" y1="140" x2="620" y2="75" stroke="rgba(224,64,251,0.07)" stroke-width="0.5"/>
|
|
32
|
+
<line x1="580" y1="150" x2="680" y2="190" stroke="rgba(83,109,254,0.07)" stroke-width="0.5"/>
|
|
33
|
+
<line x1="680" y1="190" x2="740" y2="140" stroke="rgba(224,64,251,0.06)" stroke-width="0.5"/>
|
|
34
|
+
|
|
35
|
+
<!-- Vertices -->
|
|
36
|
+
<circle cx="520" cy="45" r="2.5" fill="rgba(224,64,251,0.5)"/>
|
|
37
|
+
<circle cx="620" cy="75" r="3.5" fill="rgba(224,64,251,0.6)"/>
|
|
38
|
+
<circle cx="580" cy="150" r="2.5" fill="rgba(83,109,254,0.45)"/>
|
|
39
|
+
<circle cx="720" cy="55" r="2" fill="rgba(124,77,255,0.4)"/>
|
|
40
|
+
<circle cx="740" cy="140" r="3" fill="rgba(83,109,254,0.5)"/>
|
|
41
|
+
<circle cx="680" cy="190" r="2" fill="rgba(224,64,251,0.35)"/>
|
|
42
|
+
<circle cx="480" cy="200" r="1.5" fill="rgba(124,77,255,0.2)"/>
|
|
43
|
+
<circle cx="770" cy="25" r="1" fill="rgba(224,64,251,0.15)"/>
|
|
44
|
+
|
|
45
|
+
<!-- Title glow layer -->
|
|
46
|
+
<text x="55" y="100" font-family="'IBM Plex Mono', 'Source Code Pro', monospace" font-size="44" font-weight="600" fill="rgba(224,64,251,0.18)" filter="url(#txtGlow)">kwork-mcp</text>
|
|
47
|
+
<!-- Title sharp layer -->
|
|
48
|
+
<text x="55" y="100" font-family="'IBM Plex Mono', 'Source Code Pro', monospace" font-size="44" font-weight="600" fill="url(#ac)">kwork-mcp</text>
|
|
49
|
+
|
|
50
|
+
<!-- Tagline -->
|
|
51
|
+
<text x="57" y="135" font-family="'Outfit', 'Sora', sans-serif" font-size="13.5" fill="rgba(255,255,255,0.38)" letter-spacing="1">MCP server · 25 tools · Kwork freelance marketplace</text>
|
|
52
|
+
|
|
53
|
+
<!-- Accent dot + line -->
|
|
54
|
+
<circle cx="59" cy="155" r="2.5" fill="rgba(224,64,251,0.4)"/>
|
|
55
|
+
<rect x="68" y="153.5" width="80" height="1" fill="url(#ac)" rx="0.5" opacity="0.3"/>
|
|
56
|
+
</svg>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "kwork-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for managing Kwork freelance marketplace"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Simon Sudarushkin", email = "simonsudarushkin@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["mcp", "kwork", "freelance", "claude", "mcp-server"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Topic :: Software Development :: Libraries",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.20",
|
|
23
|
+
"kwork>=0.2.0",
|
|
24
|
+
"pydantic-settings>=2.7",
|
|
25
|
+
"loguru>=0.7",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8",
|
|
31
|
+
"pytest-asyncio>=0.25",
|
|
32
|
+
"ruff>=0.9",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
kwork-mcp = "kwork_mcp:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/simonether/kwork-mcp"
|
|
40
|
+
Repository = "https://github.com/simonether/kwork-mcp"
|
|
41
|
+
Issues = "https://github.com/simonether/kwork-mcp/issues"
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/kwork_mcp"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
target-version = "py312"
|
|
52
|
+
line-length = 120
|
|
53
|
+
src = ["src", "tests"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "W", "F", "I", "N", "UP", "B", "SIM", "T20", "RUF", "ASYNC"]
|
|
57
|
+
ignore = ["RUF001", "RUF002", "E501"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
logger.remove()
|
|
10
|
+
logger.add(sys.stderr, level="INFO", format="{time:HH:mm:ss} | {level} | {message}")
|
|
11
|
+
|
|
12
|
+
from kwork_mcp.server import create_server
|
|
13
|
+
|
|
14
|
+
server = create_server()
|
|
15
|
+
server.run(transport="stdio")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import SecretStr
|
|
6
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KworkConfig(BaseSettings):
|
|
10
|
+
model_config = SettingsConfigDict(
|
|
11
|
+
env_prefix="KWORK_",
|
|
12
|
+
env_file=".env",
|
|
13
|
+
extra="ignore",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
login: str = ""
|
|
17
|
+
password: SecretStr = SecretStr("")
|
|
18
|
+
phone_last: str | None = None
|
|
19
|
+
token: SecretStr | None = None
|
|
20
|
+
|
|
21
|
+
proxy_url: str | None = None
|
|
22
|
+
timeout: int = 30
|
|
23
|
+
|
|
24
|
+
rps_limit: int = 2
|
|
25
|
+
burst_limit: int = 5
|
|
26
|
+
|
|
27
|
+
token_file: Path = Path.home() / ".kwork_token"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
6
|
+
from kwork.exceptions import KworkException, KworkHTTPException
|
|
7
|
+
from loguru import logger
|
|
8
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_error_code(exc: KworkException) -> int | None:
|
|
12
|
+
"""Extract Kwork API error_code from KworkHTTPException.response_json."""
|
|
13
|
+
if isinstance(exc, KworkHTTPException) and exc.response_json:
|
|
14
|
+
return exc.response_json.get("error_code")
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@asynccontextmanager
|
|
19
|
+
async def api_guard(operation: str = "Kwork API") -> AsyncIterator[None]:
|
|
20
|
+
"""Context manager that translates pykwork exceptions into ToolError."""
|
|
21
|
+
try:
|
|
22
|
+
yield
|
|
23
|
+
except ToolError:
|
|
24
|
+
raise
|
|
25
|
+
except KworkHTTPException as exc:
|
|
26
|
+
code = _get_error_code(exc)
|
|
27
|
+
if code == 118 or "captcha" in str(exc).lower():
|
|
28
|
+
raise ToolError(
|
|
29
|
+
"Kwork требует капчу. Откройте kwork.ru в браузере, "
|
|
30
|
+
"решите капчу, затем передайте свежий токен через KWORK_TOKEN."
|
|
31
|
+
) from exc
|
|
32
|
+
if exc.status == 401:
|
|
33
|
+
raise ToolError(
|
|
34
|
+
"Сессия истекла, повторная авторизация не удалась. Проверьте KWORK_LOGIN и KWORK_PASSWORD."
|
|
35
|
+
) from exc
|
|
36
|
+
if exc.status == 403 or code == 403:
|
|
37
|
+
raise ToolError(
|
|
38
|
+
"IP заблокирован Kwork. Попробуйте позже или используйте прокси (KWORK_PROXY_URL)."
|
|
39
|
+
) from exc
|
|
40
|
+
logger.error("{op} HTTP error: status={status} {exc}", op=operation, status=exc.status, exc=exc)
|
|
41
|
+
raise ToolError(f"Ошибка {operation}: {exc}") from exc
|
|
42
|
+
except KworkException as exc:
|
|
43
|
+
logger.error("{op} error: {exc}", op=operation, exc=exc)
|
|
44
|
+
raise ToolError(f"Ошибка {operation}: {exc}") from exc
|
|
45
|
+
except TimeoutError as exc:
|
|
46
|
+
raise ToolError("Kwork API недоступен (таймаут). Попробуйте позже.") from exc
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
logger.exception("Unexpected error in {op}", op=operation)
|
|
49
|
+
raise ToolError(f"Непредвиденная ошибка {operation}: {exc}") from exc
|