mcp-server-mturk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_server_mturk-0.1.0.dist-info/METADATA +271 -0
- mcp_server_mturk-0.1.0.dist-info/RECORD +14 -0
- mcp_server_mturk-0.1.0.dist-info/WHEEL +4 -0
- mcp_server_mturk-0.1.0.dist-info/entry_points.txt +2 -0
- mcp_server_mturk-0.1.0.dist-info/licenses/LICENSE +21 -0
- mturk_mcp/__init__.py +3 -0
- mturk_mcp/client.py +75 -0
- mturk_mcp/server.py +75 -0
- mturk_mcp/tools/__init__.py +5 -0
- mturk_mcp/tools/account.py +109 -0
- mturk_mcp/tools/assignments.py +329 -0
- mturk_mcp/tools/hits.py +384 -0
- mturk_mcp/tools/qualifications.py +341 -0
- mturk_mcp/tools/workers.py +359 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""Worker management tools for blocking, notifying, and paying bonuses."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from mcp.types import TextContent
|
|
5
|
+
|
|
6
|
+
from ..client import get_mturk_client, format_error
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_tools(register):
|
|
10
|
+
"""Register worker-related tools."""
|
|
11
|
+
|
|
12
|
+
async def block_worker(args: dict) -> list[TextContent]:
|
|
13
|
+
"""Block a worker from accepting your HITs."""
|
|
14
|
+
try:
|
|
15
|
+
client = get_mturk_client()
|
|
16
|
+
client.create_worker_block(
|
|
17
|
+
WorkerId=args["worker_id"],
|
|
18
|
+
Reason=args["reason"],
|
|
19
|
+
)
|
|
20
|
+
return [TextContent(type="text", text=json.dumps({
|
|
21
|
+
"success": True,
|
|
22
|
+
"worker_id": args["worker_id"],
|
|
23
|
+
"action": "blocked",
|
|
24
|
+
"reason": args["reason"],
|
|
25
|
+
}, indent=2))]
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
28
|
+
|
|
29
|
+
register(
|
|
30
|
+
"block_worker",
|
|
31
|
+
"""Prevents a worker from seeing or accepting any of your HITs in the future.
|
|
32
|
+
|
|
33
|
+
Use this tool as a last resort for workers who:
|
|
34
|
+
- Consistently submit spam or gibberish
|
|
35
|
+
- Appear to be bots or automated systems
|
|
36
|
+
- Violate your task guidelines repeatedly despite feedback
|
|
37
|
+
- Exhibit clearly bad-faith behavior
|
|
38
|
+
|
|
39
|
+
IMPORTANT CONSIDERATIONS:
|
|
40
|
+
- Blocking is account-wide - the worker can't do ANY of your HITs, not just the problematic one
|
|
41
|
+
- The worker is NOT notified they're blocked - they simply won't see your HITs
|
|
42
|
+
- The reason you provide is for YOUR records only (worker doesn't see it)
|
|
43
|
+
- Consider using qualification-based exclusion instead for less severe cases
|
|
44
|
+
|
|
45
|
+
Blocking is more severe than rejection. Rejection affects one assignment; blocking affects all future interactions. Use rejection + feedback first to give workers a chance to improve.
|
|
46
|
+
|
|
47
|
+
To reverse a block, use unblock_worker. You can see all blocked workers with list_blocked_workers.""",
|
|
48
|
+
{
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"worker_id": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "The worker ID to block. Get this from list_assignments. Once blocked, this worker cannot see or accept any of your HITs."
|
|
54
|
+
},
|
|
55
|
+
"reason": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Your internal note explaining why you blocked this worker. The worker does NOT see this. Keep it factual for your records. Example: 'Submitted random characters on 5 consecutive assignments'"
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
"required": ["worker_id", "reason"],
|
|
61
|
+
},
|
|
62
|
+
block_worker,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def unblock_worker(args: dict) -> list[TextContent]:
|
|
66
|
+
"""Remove a block from a worker, allowing them to work on your HITs again."""
|
|
67
|
+
try:
|
|
68
|
+
client = get_mturk_client()
|
|
69
|
+
params = {"WorkerId": args["worker_id"]}
|
|
70
|
+
if args.get("reason"):
|
|
71
|
+
params["Reason"] = args["reason"]
|
|
72
|
+
|
|
73
|
+
client.delete_worker_block(**params)
|
|
74
|
+
return [TextContent(type="text", text=json.dumps({
|
|
75
|
+
"success": True,
|
|
76
|
+
"worker_id": args["worker_id"],
|
|
77
|
+
"action": "unblocked",
|
|
78
|
+
}, indent=2))]
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
81
|
+
|
|
82
|
+
register(
|
|
83
|
+
"unblock_worker",
|
|
84
|
+
"""Removes a block from a worker, allowing them to see and accept your HITs again.
|
|
85
|
+
|
|
86
|
+
Use this tool when:
|
|
87
|
+
- You blocked a worker by mistake
|
|
88
|
+
- A worker reached out and explained their situation
|
|
89
|
+
- Enough time has passed that you want to give them another chance
|
|
90
|
+
- You're cleaning up old blocks that are no longer relevant
|
|
91
|
+
|
|
92
|
+
The worker is not notified when unblocked - they'll simply start seeing your HITs again.
|
|
93
|
+
|
|
94
|
+
To find workers you've blocked, use list_blocked_workers. The reason field is optional and for your records only.""",
|
|
95
|
+
{
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"worker_id": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "The worker ID to unblock. Get this from list_blocked_workers."
|
|
101
|
+
},
|
|
102
|
+
"reason": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Optional note for your records about why you're unblocking. Example: 'Worker apologized and promised to follow guidelines'"
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
"required": ["worker_id"],
|
|
108
|
+
},
|
|
109
|
+
unblock_worker,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def list_blocked_workers(args: dict) -> list[TextContent]:
|
|
113
|
+
"""List all workers you have blocked."""
|
|
114
|
+
try:
|
|
115
|
+
client = get_mturk_client()
|
|
116
|
+
response = client.list_worker_blocks(MaxResults=min(args.get("max_results", 100), 100))
|
|
117
|
+
blocks = response.get("WorkerBlocks", [])
|
|
118
|
+
result = {
|
|
119
|
+
"count": len(blocks),
|
|
120
|
+
"blocked_workers": [
|
|
121
|
+
{
|
|
122
|
+
"worker_id": b["WorkerId"],
|
|
123
|
+
"reason": b.get("Reason"),
|
|
124
|
+
}
|
|
125
|
+
for b in blocks
|
|
126
|
+
],
|
|
127
|
+
}
|
|
128
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
129
|
+
except Exception as e:
|
|
130
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
131
|
+
|
|
132
|
+
register(
|
|
133
|
+
"list_blocked_workers",
|
|
134
|
+
"""Returns a list of all workers you have blocked from your HITs.
|
|
135
|
+
|
|
136
|
+
Use this tool to:
|
|
137
|
+
- Review who you've blocked and why (the reason you provided when blocking)
|
|
138
|
+
- Find worker IDs for workers you want to unblock
|
|
139
|
+
- Audit your blocklist periodically to clean up old blocks
|
|
140
|
+
|
|
141
|
+
Each entry shows the worker_id and the reason you recorded when blocking them.
|
|
142
|
+
|
|
143
|
+
Note: Only returns up to 100 blocked workers. If you have more, you may need to paginate or clean up old blocks.""",
|
|
144
|
+
{
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"max_results": {
|
|
148
|
+
"type": "integer",
|
|
149
|
+
"description": "Maximum number of blocked workers to return, between 1 and 100. Defaults to 100.",
|
|
150
|
+
"default": 100,
|
|
151
|
+
"minimum": 1,
|
|
152
|
+
"maximum": 100
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
"required": [],
|
|
156
|
+
},
|
|
157
|
+
list_blocked_workers,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def notify_workers(args: dict) -> list[TextContent]:
|
|
161
|
+
"""Send an email notification to one or more workers."""
|
|
162
|
+
try:
|
|
163
|
+
client = get_mturk_client()
|
|
164
|
+
worker_list = [w.strip() for w in args["worker_ids"].split(",")]
|
|
165
|
+
|
|
166
|
+
client.notify_workers(
|
|
167
|
+
Subject=args["subject"],
|
|
168
|
+
MessageText=args["message"],
|
|
169
|
+
WorkerIds=worker_list,
|
|
170
|
+
)
|
|
171
|
+
return [TextContent(type="text", text=json.dumps({
|
|
172
|
+
"success": True,
|
|
173
|
+
"workers_notified": worker_list,
|
|
174
|
+
"subject": args["subject"],
|
|
175
|
+
}, indent=2))]
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
178
|
+
|
|
179
|
+
register(
|
|
180
|
+
"notify_workers",
|
|
181
|
+
"""Sends an email message to one or more workers through MTurk's messaging system.
|
|
182
|
+
|
|
183
|
+
Use this tool to:
|
|
184
|
+
- Thank workers for excellent work
|
|
185
|
+
- Invite trusted workers to new HITs
|
|
186
|
+
- Clarify task instructions if many workers are confused
|
|
187
|
+
- Apologize for issues with your HITs
|
|
188
|
+
- Request workers return for follow-up studies
|
|
189
|
+
|
|
190
|
+
IMPORTANT GUIDELINES:
|
|
191
|
+
- Workers can block requesters who send too many messages - use sparingly
|
|
192
|
+
- Messages come from your MTurk requester account, not a personal email
|
|
193
|
+
- Keep messages professional and relevant to work
|
|
194
|
+
- Don't use for marketing unrelated to your HITs
|
|
195
|
+
|
|
196
|
+
The message goes to workers' MTurk-associated email addresses. They may have filters or not check frequently, so don't rely on immediate responses.
|
|
197
|
+
|
|
198
|
+
Subject lines should be clear and professional. Messages should be concise and actionable.
|
|
199
|
+
|
|
200
|
+
Example use: After a successful survey, message participants: "Thank you for completing our research survey. We've posted a follow-up study you may be interested in." """,
|
|
201
|
+
{
|
|
202
|
+
"type": "object",
|
|
203
|
+
"properties": {
|
|
204
|
+
"worker_ids": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"description": "Comma-separated list of worker IDs to message. Example: 'A1B2C3D4E5,F6G7H8I9J0'. Get worker IDs from list_assignments. Maximum varies but keep it reasonable (under 100)."
|
|
207
|
+
},
|
|
208
|
+
"subject": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Email subject line. Keep it professional and clear. Example: 'Thank you for your survey response' or 'New HIT available that may interest you'"
|
|
211
|
+
},
|
|
212
|
+
"message": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "The message body. Keep it concise and relevant to their work. Include any action items clearly. Be professional - this reflects on you as a requester."
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
"required": ["worker_ids", "subject", "message"],
|
|
218
|
+
},
|
|
219
|
+
notify_workers,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
async def send_bonus(args: dict) -> list[TextContent]:
|
|
223
|
+
"""Send a bonus payment to a worker for a specific assignment."""
|
|
224
|
+
try:
|
|
225
|
+
client = get_mturk_client()
|
|
226
|
+
client.send_bonus(
|
|
227
|
+
WorkerId=args["worker_id"],
|
|
228
|
+
AssignmentId=args["assignment_id"],
|
|
229
|
+
BonusAmount=args["bonus_amount"],
|
|
230
|
+
Reason=args["reason"],
|
|
231
|
+
)
|
|
232
|
+
return [TextContent(type="text", text=json.dumps({
|
|
233
|
+
"success": True,
|
|
234
|
+
"worker_id": args["worker_id"],
|
|
235
|
+
"assignment_id": args["assignment_id"],
|
|
236
|
+
"bonus_amount": args["bonus_amount"],
|
|
237
|
+
"reason": args["reason"],
|
|
238
|
+
}, indent=2))]
|
|
239
|
+
except Exception as e:
|
|
240
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
241
|
+
|
|
242
|
+
register(
|
|
243
|
+
"send_bonus",
|
|
244
|
+
"""Sends an additional payment (bonus) to a worker for a specific assignment they completed.
|
|
245
|
+
|
|
246
|
+
Use this tool to:
|
|
247
|
+
- Reward exceptionally high-quality work
|
|
248
|
+
- Compensate workers for extra effort or time
|
|
249
|
+
- Pay for legitimate work on a HIT you had to reject for technical reasons
|
|
250
|
+
- Incentivize workers in longitudinal studies
|
|
251
|
+
- Make up for your own mistakes (e.g., unclear instructions that caused rework)
|
|
252
|
+
|
|
253
|
+
IMPORTANT:
|
|
254
|
+
- Bonuses cost real money - they're charged to your account immediately
|
|
255
|
+
- MTurk charges fees on bonuses (typically 20%)
|
|
256
|
+
- Workers LOVE bonuses - they're great for building a reliable workforce
|
|
257
|
+
- The reason you provide IS shown to the worker
|
|
258
|
+
|
|
259
|
+
Bonuses must be tied to a specific assignment (you can't bonus someone randomly). The assignment must be approved or rejected already - you can't bonus pending work.
|
|
260
|
+
|
|
261
|
+
Good bonus practices:
|
|
262
|
+
- Be specific about why: "Bonus for providing detailed explanations" not just "Good work"
|
|
263
|
+
- Fair bonuses for extra work build trust and encourage quality
|
|
264
|
+
- Even small bonuses ($0.10-0.25) are appreciated and remembered""",
|
|
265
|
+
{
|
|
266
|
+
"type": "object",
|
|
267
|
+
"properties": {
|
|
268
|
+
"worker_id": {
|
|
269
|
+
"type": "string",
|
|
270
|
+
"description": "The worker ID to send the bonus to. Must match the worker who completed the assignment."
|
|
271
|
+
},
|
|
272
|
+
"assignment_id": {
|
|
273
|
+
"type": "string",
|
|
274
|
+
"description": "The assignment ID this bonus is for. The assignment must exist and be completed (approved or rejected). Get from list_assignments."
|
|
275
|
+
},
|
|
276
|
+
"bonus_amount": {
|
|
277
|
+
"type": "string",
|
|
278
|
+
"description": "Bonus amount in USD as a string. Example: '0.50' for 50 cents, '2.00' for $2.00. Must be at least $0.01. You'll also pay MTurk fees on top of this."
|
|
279
|
+
},
|
|
280
|
+
"reason": {
|
|
281
|
+
"type": "string",
|
|
282
|
+
"description": "Message explaining why you're giving this bonus. THE WORKER SEES THIS. Be specific and appreciative. Example: 'Thank you for providing such detailed and thoughtful responses!'"
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
"required": ["worker_id", "assignment_id", "bonus_amount", "reason"],
|
|
286
|
+
},
|
|
287
|
+
send_bonus,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
async def list_bonus_payments(args: dict) -> list[TextContent]:
|
|
291
|
+
"""List bonus payments made. Must specify either hit_id or assignment_id."""
|
|
292
|
+
try:
|
|
293
|
+
if not args.get("hit_id") and not args.get("assignment_id"):
|
|
294
|
+
return [TextContent(type="text", text="Error: Must specify either hit_id or assignment_id")]
|
|
295
|
+
|
|
296
|
+
client = get_mturk_client()
|
|
297
|
+
params = {"MaxResults": min(args.get("max_results", 100), 100)}
|
|
298
|
+
if args.get("hit_id"):
|
|
299
|
+
params["HITId"] = args["hit_id"]
|
|
300
|
+
if args.get("assignment_id"):
|
|
301
|
+
params["AssignmentId"] = args["assignment_id"]
|
|
302
|
+
|
|
303
|
+
response = client.list_bonus_payments(**params)
|
|
304
|
+
payments = response.get("BonusPayments", [])
|
|
305
|
+
result = {
|
|
306
|
+
"count": len(payments),
|
|
307
|
+
"bonus_payments": [
|
|
308
|
+
{
|
|
309
|
+
"worker_id": p["WorkerId"],
|
|
310
|
+
"assignment_id": p["AssignmentId"],
|
|
311
|
+
"bonus_amount": p["BonusAmount"],
|
|
312
|
+
"reason": p.get("Reason"),
|
|
313
|
+
"grant_time": str(p.get("GrantTime")),
|
|
314
|
+
}
|
|
315
|
+
for p in payments
|
|
316
|
+
],
|
|
317
|
+
}
|
|
318
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
319
|
+
except Exception as e:
|
|
320
|
+
return [TextContent(type="text", text=format_error(e))]
|
|
321
|
+
|
|
322
|
+
register(
|
|
323
|
+
"list_bonus_payments",
|
|
324
|
+
"""Lists bonus payments you've made, filtered by HIT or assignment.
|
|
325
|
+
|
|
326
|
+
Use this tool to:
|
|
327
|
+
- Audit bonus spending on a specific HIT
|
|
328
|
+
- Verify a bonus was sent successfully
|
|
329
|
+
- Track how much you've bonused across a project
|
|
330
|
+
- Ensure you haven't accidentally double-bonused a worker
|
|
331
|
+
|
|
332
|
+
You MUST provide either a hit_id (to see all bonuses for that HIT) or an assignment_id (to see bonuses for that specific assignment). Cannot list all bonuses across your entire account.
|
|
333
|
+
|
|
334
|
+
Returns the amount, worker, assignment, reason, and timestamp for each bonus payment.
|
|
335
|
+
|
|
336
|
+
Note: This shows bonuses YOU sent. It doesn't show the base HIT payment, only additional bonuses.""",
|
|
337
|
+
{
|
|
338
|
+
"type": "object",
|
|
339
|
+
"properties": {
|
|
340
|
+
"hit_id": {
|
|
341
|
+
"type": "string",
|
|
342
|
+
"description": "List bonuses for all assignments in this HIT. Provide either this OR assignment_id, not both."
|
|
343
|
+
},
|
|
344
|
+
"assignment_id": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"description": "List bonuses for this specific assignment only. Provide either this OR hit_id, not both."
|
|
347
|
+
},
|
|
348
|
+
"max_results": {
|
|
349
|
+
"type": "integer",
|
|
350
|
+
"description": "Maximum number of bonus payments to return, between 1 and 100. Defaults to 100.",
|
|
351
|
+
"default": 100,
|
|
352
|
+
"minimum": 1,
|
|
353
|
+
"maximum": 100
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
"required": [],
|
|
357
|
+
},
|
|
358
|
+
list_bonus_payments,
|
|
359
|
+
)
|