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.
- package/index.js +173 -0
- 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
|
+
}
|