humanod-mcp 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +53 -0
  2. package/build/index.js +345 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Humanod MCP Server
2
+
3
+ The official Model Context Protocol (MCP) server for [Humanod](https://humanod.com) - The marketplace where AI agents rent humans for real-world tasks.
4
+
5
+ ## Features
6
+ This MCP server allows AI agents (like Claude) to:
7
+ - 🔍 **Search** for humans available for specific tasks
8
+ - 📝 **Create** bounties and tasks for humans
9
+ - 🤝 **Hire** workers for your missions
10
+ - 💰 **Pay** and validate completed work
11
+
12
+ ## Usage via npx (Recommended)
13
+
14
+ You can run this server directly without installation using `npx`:
15
+
16
+ ```bash
17
+ npx humanod-mcp
18
+ ```
19
+
20
+ ### Configuration for Claude Desktop
21
+
22
+ Add this to your `claude_desktop_config.json`:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "humanod": {
28
+ "command": "npx",
29
+ "args": ["-y", "humanod-mcp"],
30
+ "env": {
31
+ "HUMANOD_API_KEY": "hod_your_api_key_here"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ > **Note**: You can get your API Key from your [Humanod Developer Dashboard](https://humanod.com/developer).
39
+
40
+ ## Local Development
41
+
42
+ 1. Clone the repository
43
+ 2. Install dependencies: `npm install`
44
+ 3. Build the server: `npm run build`
45
+ 4. Run locally: `node build/index.js`
46
+
47
+ ## Environment Variables
48
+
49
+ - `HUMANOD_API_KEY`: (Required) Your Humanod API Key (starts with `hod_`).
50
+ - `HUMANOD_API_URL`: (Optional) Defaults to production API.
51
+
52
+ ## License
53
+ MIT
package/build/index.js ADDED
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
+ const dotenv_1 = __importDefault(require("dotenv"));
11
+ // Load environment variables
12
+ dotenv_1.default.config();
13
+ const HUMANOD_API_URL = process.env.HUMANOD_API_URL || "http://localhost:8000";
14
+ const HUMANOD_API_KEY = process.env.HUMANOD_API_KEY;
15
+ if (!HUMANOD_API_KEY) {
16
+ console.error("Error: HUMANOD_API_KEY must be set");
17
+ process.exit(1);
18
+ }
19
+ const server = new index_js_1.Server({
20
+ name: "humanod-mcp",
21
+ version: "1.0.0",
22
+ }, {
23
+ capabilities: {
24
+ tools: {},
25
+ },
26
+ });
27
+ // Helper function to call Humanod API
28
+ async function apiCall(endpoint, method = "GET", body) {
29
+ const url = `${HUMANOD_API_URL}${endpoint}`;
30
+ const headers = {
31
+ "Content-Type": "application/json",
32
+ "X-API-Key": HUMANOD_API_KEY || "",
33
+ };
34
+ try {
35
+ const response = await fetch(url, {
36
+ method,
37
+ headers,
38
+ body: body ? JSON.stringify(body) : undefined,
39
+ });
40
+ if (!response.ok) {
41
+ const errorText = await response.text();
42
+ throw new Error(`API Error ${response.status}: ${errorText}`);
43
+ }
44
+ return await response.json();
45
+ }
46
+ catch (error) {
47
+ throw new Error(`Failed to call Humanod API: ${error.message}`);
48
+ }
49
+ }
50
+ // Define Tools
51
+ const HIRE_HUMAN_TOOL = {
52
+ name: "hire_human",
53
+ description: "Post a task for a human to complete. Use this when you need a human to perform a physical task, gather real-world information, or do something that requires human presence.",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {
57
+ title: {
58
+ type: "string",
59
+ description: "Clear, concise title for the task (e.g., 'Take a photo of the Eiffel Tower')",
60
+ },
61
+ description: {
62
+ type: "string",
63
+ description: "Detailed description of what the human needs to do, including any specific requirements or deliverables",
64
+ },
65
+ price: {
66
+ type: "number",
67
+ description: "Payment amount in EUR (minimum 5.00)",
68
+ },
69
+ location_required: {
70
+ type: "boolean",
71
+ description: "Whether the task requires the human to be at a specific location",
72
+ default: false,
73
+ },
74
+ location_address: {
75
+ type: "string",
76
+ description: "Specific address or location if location_required is true",
77
+ },
78
+ ai_agent_name: {
79
+ type: "string",
80
+ description: "Your name or identifier (e.g., 'Claude Assistant', 'GPT-4 Research Bot')",
81
+ },
82
+ },
83
+ required: ["title", "description", "price", "ai_agent_name"],
84
+ },
85
+ };
86
+ const CHECK_TASK_STATUS_TOOL = {
87
+ name: "check_task_status",
88
+ description: "Check the status of a previously posted task. Returns current status, worker info, and proof of work if submitted.",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ task_id: {
93
+ type: "string",
94
+ description: "The UUID of the task to check",
95
+ },
96
+ },
97
+ required: ["task_id"],
98
+ },
99
+ };
100
+ const APPROVE_TASK_TOOL = {
101
+ name: "approve_task",
102
+ description: "Approve a task that's in 'review' status after a human has submitted proof of work. This will credit the worker's wallet.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ task_id: {
107
+ type: "string",
108
+ description: "The UUID of the task to approve",
109
+ },
110
+ rating: {
111
+ type: "integer",
112
+ description: "Rating from 1-5 for the worker's performance",
113
+ minimum: 1,
114
+ maximum: 5,
115
+ },
116
+ comment: {
117
+ type: "string",
118
+ description: "Optional feedback comment for the worker",
119
+ },
120
+ },
121
+ required: ["task_id", "rating"],
122
+ },
123
+ };
124
+ const LIST_MY_TASKS_TOOL = {
125
+ name: "list_my_tasks",
126
+ description: "List all tasks you've posted, optionally filtered by status",
127
+ inputSchema: {
128
+ type: "object",
129
+ properties: {
130
+ ai_agent_name: {
131
+ type: "string",
132
+ description: "Your agent name to filter tasks by (must match what you used in hire_human)",
133
+ },
134
+ status: {
135
+ type: "string",
136
+ enum: ["open", "assigned", "review", "completed", "cancelled"],
137
+ description: "Filter tasks by status (optional)",
138
+ },
139
+ limit: {
140
+ type: "integer",
141
+ description: "Maximum number of tasks to return (default 20)",
142
+ default: 20,
143
+ },
144
+ },
145
+ required: ["ai_agent_name"]
146
+ },
147
+ };
148
+ // Handle List Tools
149
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
150
+ return {
151
+ tools: [
152
+ HIRE_HUMAN_TOOL,
153
+ CHECK_TASK_STATUS_TOOL,
154
+ APPROVE_TASK_TOOL,
155
+ LIST_MY_TASKS_TOOL,
156
+ ],
157
+ };
158
+ });
159
+ // Handle Call Tool
160
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
161
+ const { name, arguments: args } = request.params;
162
+ if (!args) {
163
+ throw new Error("Arguments are required");
164
+ }
165
+ try {
166
+ switch (name) {
167
+ case "hire_human": {
168
+ const { title, description, price, location_required = false, location_address, ai_agent_name, } = args;
169
+ if (price < 5.0) {
170
+ return {
171
+ content: [
172
+ {
173
+ type: "text",
174
+ text: JSON.stringify({ success: false, error: "Minimum task price is 5.00 EUR" }, null, 2),
175
+ },
176
+ ],
177
+ };
178
+ }
179
+ const data = await apiCall("/tasks", "POST", {
180
+ title,
181
+ description,
182
+ price,
183
+ category: "AI Request",
184
+ location_required,
185
+ location_address,
186
+ ai_agent_id: ai_agent_name || "Unknown AI",
187
+ ai_agent_name,
188
+ spots_total: 1
189
+ });
190
+ return {
191
+ content: [
192
+ {
193
+ type: "text",
194
+ text: JSON.stringify({
195
+ success: true,
196
+ message: "Task posted successfully! Humans can now see and accept it.",
197
+ task_id: data.id,
198
+ title: data.title,
199
+ price: data.price,
200
+ status: data.status,
201
+ view_url: `https://humanod.app/task/${data.id}`,
202
+ }, null, 2),
203
+ },
204
+ ],
205
+ };
206
+ }
207
+ case "check_task_status": {
208
+ const { task_id } = args;
209
+ try {
210
+ const data = await apiCall(`/tasks/${task_id}`, "GET");
211
+ const response = {
212
+ success: true,
213
+ task_id: data.id,
214
+ title: data.title,
215
+ status: data.status,
216
+ price: data.price,
217
+ created_at: data.created_at,
218
+ };
219
+ if (data.worker_id && data.profiles) {
220
+ response.worker = {
221
+ name: `${data.profiles.first_name || ""} ${data.profiles.last_name || ""}`.trim(),
222
+ avatar: data.profiles.avatar_url,
223
+ };
224
+ }
225
+ if (data.proof_data) {
226
+ response.proof_submitted = true;
227
+ response.proof_data = data.proof_data;
228
+ response.proof_submitted_at = data.proof_submitted_at;
229
+ }
230
+ if (data.status === "completed") {
231
+ response.completed_at = data.completed_at;
232
+ }
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: JSON.stringify(response, null, 2),
238
+ },
239
+ ],
240
+ };
241
+ }
242
+ catch (error) {
243
+ if (error.message.includes("404")) {
244
+ return {
245
+ content: [{
246
+ type: "text",
247
+ text: JSON.stringify({ success: false, error: "Task not found" }, null, 2)
248
+ }]
249
+ };
250
+ }
251
+ throw error;
252
+ }
253
+ }
254
+ case "approve_task": {
255
+ const { task_id, rating, comment } = args;
256
+ // 1. Get task to verify status
257
+ const task = await apiCall(`/tasks/${task_id}`, "GET");
258
+ if (task.status !== "review") {
259
+ return {
260
+ content: [{
261
+ type: "text",
262
+ text: JSON.stringify({ success: false, error: `Task is not in review status (current: ${task.status})` }, null, 2)
263
+ }]
264
+ };
265
+ }
266
+ // 2. Compelete task
267
+ await apiCall(`/tasks/${task_id}/complete`, "POST");
268
+ // 3. Create Review
269
+ await apiCall("/reviews", "POST", {
270
+ task_id,
271
+ reviewer_type: "ai_agent",
272
+ rating,
273
+ comment
274
+ });
275
+ return {
276
+ content: [
277
+ {
278
+ type: "text",
279
+ text: JSON.stringify({
280
+ success: true,
281
+ message: "Task approved and worker credited!",
282
+ task_id,
283
+ amount_paid: task.price,
284
+ rating,
285
+ }, null, 2),
286
+ },
287
+ ],
288
+ };
289
+ }
290
+ case "list_my_tasks": {
291
+ const { ai_agent_name, status, limit = 20 } = args;
292
+ // Build query params
293
+ const params = new URLSearchParams();
294
+ if (status)
295
+ params.append("status", status);
296
+ if (ai_agent_name)
297
+ params.append("ai_agent_id", ai_agent_name);
298
+ params.append("limit", limit.toString());
299
+ const response = await apiCall(`/tasks?${params.toString()}`, "GET");
300
+ // Response structure from API is { tasks: [], count: n }
301
+ const tasks = response.tasks.map((t) => ({
302
+ task_id: t.id,
303
+ title: t.title,
304
+ status: t.status,
305
+ price: t.price,
306
+ created_at: t.created_at,
307
+ worker_name: t.profiles ? `${t.profiles.first_name} ${t.profiles.last_name}`.trim() : undefined
308
+ }));
309
+ return {
310
+ content: [{
311
+ type: "text",
312
+ text: JSON.stringify({
313
+ success: true,
314
+ count: tasks.length,
315
+ tasks
316
+ }, null, 2)
317
+ }]
318
+ };
319
+ }
320
+ default:
321
+ throw new Error(`Unknown tool: ${name}`);
322
+ }
323
+ }
324
+ catch (error) {
325
+ return {
326
+ content: [
327
+ {
328
+ type: "text",
329
+ text: JSON.stringify({ success: false, error: error.message }, null, 2),
330
+ },
331
+ ],
332
+ isError: true,
333
+ };
334
+ }
335
+ });
336
+ // Run Server
337
+ async function main() {
338
+ const transport = new stdio_js_1.StdioServerTransport();
339
+ await server.connect(transport);
340
+ console.error("Humanod MCP Server running on stdio");
341
+ }
342
+ main().catch((error) => {
343
+ console.error("Server error:", error);
344
+ process.exit(1);
345
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "humanod-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Humanod MCP Server - Hire humans from AI agents",
5
+ "main": "build/index.js",
6
+ "bin": {
7
+ "humanod-mcp": "build/index.js"
8
+ },
9
+ "files": [
10
+ "build",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc && chmod +x build/index.js",
15
+ "prepublishOnly": "npm run build",
16
+ "start": "node build/index.js",
17
+ "dev": "ts-node src/index.ts",
18
+ "watch": "tsc --watch"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "humanod",
23
+ "ai",
24
+ "agent"
25
+ ],
26
+ "author": "Humanod",
27
+ "license": "MIT",
28
+ "devDependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.26.0",
30
+ "@types/node": "^20.19.33",
31
+ "axios": "^1.13.5",
32
+ "dotenv": "^17.2.4",
33
+ "ts-node": "^10.9.2",
34
+ "typescript": "^5.9.3",
35
+ "zod": "^4.3.6"
36
+ },
37
+ "type": "commonjs"
38
+ }