loopuman-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 (2) hide show
  1. package/index.js +173 -0
  2. package/package.json +12 -0
package/index.js ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Loopuman MCP Server
4
+ * Gives Claude native access to human workers
5
+ *
6
+ * Usage:
7
+ * 1. npm install -g loopuman-mcp
8
+ * 2. Add to Claude config:
9
+ * {
10
+ * "mcpServers": {
11
+ * "loopuman": {
12
+ * "command": "loopuman-mcp",
13
+ * "env": { "LOOPUMAN_API_KEY": "your_key" }
14
+ * }
15
+ * }
16
+ * }
17
+ * 3. Ask Claude: "Ask a human if this text is offensive"
18
+ */
19
+
20
+ const API_BASE = process.env.LOOPUMAN_API_URL || 'https://api.loopuman.com';
21
+ const API_KEY = process.env.LOOPUMAN_API_KEY;
22
+
23
+ // MCP Protocol implementation
24
+ const tools = [
25
+ {
26
+ name: "ask_human",
27
+ description: "Ask a human worker to complete a task. Use for verification, judgment, subjective evaluation, or real-world actions. Returns the human's response.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ question: {
32
+ type: "string",
33
+ description: "The question or task for the human"
34
+ },
35
+ context: {
36
+ type: "string",
37
+ description: "Additional context to help the human understand"
38
+ },
39
+ budget_cents: {
40
+ type: "number",
41
+ description: "Payment in cents (minimum 10, typical 25-100)",
42
+ default: 50
43
+ },
44
+ timeout_seconds: {
45
+ type: "number",
46
+ description: "Max time to wait for response",
47
+ default: 300
48
+ }
49
+ },
50
+ required: ["question"]
51
+ }
52
+ },
53
+ {
54
+ name: "human_verification",
55
+ description: "Get multiple humans to verify something and return consensus",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: {
59
+ content: {
60
+ type: "string",
61
+ description: "Content to verify"
62
+ },
63
+ question: {
64
+ type: "string",
65
+ description: "Verification question (e.g., 'Is this accurate?')"
66
+ },
67
+ num_workers: {
68
+ type: "number",
69
+ description: "Number of humans to ask (3-9)",
70
+ default: 3
71
+ }
72
+ },
73
+ required: ["content", "question"]
74
+ }
75
+ }
76
+ ];
77
+
78
+ async function callLoopuman(title, description, budget, timeout) {
79
+ const response = await fetch(`${API_BASE}/api/v1/tasks/sync`, {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'X-API-Key': API_KEY
84
+ },
85
+ body: JSON.stringify({
86
+ title,
87
+ description,
88
+ budget,
89
+ timeout_seconds: timeout,
90
+ auto_approve: true
91
+ })
92
+ });
93
+ return response.json();
94
+ }
95
+
96
+ async function handleToolCall(name, args) {
97
+ switch (name) {
98
+ case 'ask_human': {
99
+ const result = await callLoopuman(
100
+ args.question,
101
+ args.context || '',
102
+ args.budget_cents || 50,
103
+ args.timeout_seconds || 300
104
+ );
105
+ if (result.status === 'completed') {
106
+ return { response: result.response, worker_id: result.worker_id };
107
+ }
108
+ return { error: 'No human responded in time', task_id: result.task_id };
109
+ }
110
+
111
+ case 'human_verification': {
112
+ // Create multiple tasks and aggregate
113
+ const results = [];
114
+ for (let i = 0; i < (args.num_workers || 3); i++) {
115
+ const result = await callLoopuman(
116
+ `Verify: ${args.question}`,
117
+ `Content to verify:\n${args.content}`,
118
+ 30,
119
+ 300
120
+ );
121
+ if (result.status === 'completed') {
122
+ results.push(result.response);
123
+ }
124
+ }
125
+ return {
126
+ responses: results,
127
+ consensus: results.length > 0 ? mostCommon(results) : null
128
+ };
129
+ }
130
+
131
+ default:
132
+ return { error: `Unknown tool: ${name}` };
133
+ }
134
+ }
135
+
136
+ function mostCommon(arr) {
137
+ const counts = {};
138
+ arr.forEach(x => counts[x] = (counts[x] || 0) + 1);
139
+ return Object.entries(counts).sort((a,b) => b[1] - a[1])[0]?.[0];
140
+ }
141
+
142
+ // MCP stdio protocol
143
+ const readline = require('readline');
144
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
145
+
146
+ process.stdout.write(JSON.stringify({
147
+ protocolVersion: '2024-11-05',
148
+ capabilities: { tools: {} },
149
+ serverInfo: { name: 'loopuman', version: '1.0.0' }
150
+ }) + '\n');
151
+
152
+ rl.on('line', async (line) => {
153
+ try {
154
+ const msg = JSON.parse(line);
155
+
156
+ if (msg.method === 'tools/list') {
157
+ process.stdout.write(JSON.stringify({
158
+ id: msg.id,
159
+ result: { tools }
160
+ }) + '\n');
161
+ }
162
+
163
+ if (msg.method === 'tools/call') {
164
+ const result = await handleToolCall(msg.params.name, msg.params.arguments);
165
+ process.stdout.write(JSON.stringify({
166
+ id: msg.id,
167
+ result: { content: [{ type: 'text', text: JSON.stringify(result) }] }
168
+ }) + '\n');
169
+ }
170
+ } catch (e) {
171
+ console.error('Error:', e);
172
+ }
173
+ });
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "loopuman-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Loopuman - Give Claude access to humans",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "loopuman-mcp": "./index.js"
8
+ },
9
+ "keywords": ["mcp", "claude", "anthropic", "human-in-the-loop", "loopuman"],
10
+ "author": "Loopuman",
11
+ "license": "MIT"
12
+ }