@runhuman/mcp-server 2.0.3 → 2.0.4
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/.env.example +12 -12
- package/README.md +243 -240
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -382
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +9 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +8 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-server-factory.d.ts +10 -0
- package/dist/mcp-server-factory.d.ts.map +1 -0
- package/dist/mcp-server-factory.js +50 -0
- package/dist/mcp-server-factory.js.map +1 -0
- package/dist/tools/create-job.tool.d.ts +14 -0
- package/dist/tools/create-job.tool.d.ts.map +1 -0
- package/dist/tools/create-job.tool.js +132 -0
- package/dist/tools/create-job.tool.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/wait-for-result.tool.d.ts +14 -0
- package/dist/tools/wait-for-result.tool.d.ts.map +1 -0
- package/dist/tools/wait-for-result.tool.js +196 -0
- package/dist/tools/wait-for-result.tool.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -62
package/dist/index.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* RunHuman MCP Server
|
|
3
|
+
* RunHuman MCP Server - stdio entry point
|
|
4
4
|
*
|
|
5
|
-
* This
|
|
6
|
-
* It
|
|
7
|
-
* - Create QA jobs
|
|
8
|
-
* - Check job status
|
|
9
|
-
* - Retrieve job results
|
|
5
|
+
* This is the CLI entry point for npx @runhuman/mcp-server.
|
|
6
|
+
* It uses stdio transport for communication with MCP clients like Claude Desktop.
|
|
10
7
|
*
|
|
11
8
|
* @see https://modelcontextprotocol.io/
|
|
12
9
|
*/
|
|
13
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
14
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
16
11
|
import * as dotenv from 'dotenv';
|
|
12
|
+
import { createMcpServer } from './mcp-server-factory.js';
|
|
17
13
|
// Load environment variables (optional - for standalone testing)
|
|
18
14
|
dotenv.config();
|
|
19
15
|
// Configuration
|
|
@@ -42,382 +38,13 @@ if (!API_KEY) {
|
|
|
42
38
|
}
|
|
43
39
|
console.error(`🔗 Connected to RunHuman API at: ${API_URL}`);
|
|
44
40
|
console.error(`🔑 Using API key: ${API_KEY.substring(0, 12)}...`);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
name: 'runhuman-mcp-server',
|
|
50
|
-
version: '1.0.0',
|
|
51
|
-
}, {
|
|
52
|
-
capabilities: {
|
|
53
|
-
tools: {},
|
|
54
|
-
prompts: {},
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
/**
|
|
58
|
-
* List available prompts (documentation for the agent)
|
|
59
|
-
*/
|
|
60
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
61
|
-
return {
|
|
62
|
-
prompts: [
|
|
63
|
-
{
|
|
64
|
-
name: 'explain_runhuman',
|
|
65
|
-
description: 'Get an explanation of how to use RunHuman for QA testing',
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
/**
|
|
71
|
-
* List available tools
|
|
72
|
-
*/
|
|
73
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
74
|
-
return {
|
|
75
|
-
tools: [
|
|
76
|
-
{
|
|
77
|
-
name: 'create_job',
|
|
78
|
-
description: `⚠️ IMPORTANT: This ONLY creates and queues a job. It does NOT perform the test or return results. You MUST follow up with wait_for_result.
|
|
79
|
-
|
|
80
|
-
Creates a QA job that will be performed by a REAL HUMAN tester (not AI). The human will manually test your application, describe findings in natural language, and GPT-4o will extract structured data from their response.
|
|
81
|
-
|
|
82
|
-
Use this when you need human verification of:
|
|
83
|
-
- UI/UX functionality that's hard to automate
|
|
84
|
-
- Visual issues, accessibility problems
|
|
85
|
-
- Complex user flows (login, checkout, forms)
|
|
86
|
-
- Cross-browser compatibility
|
|
87
|
-
- Real user experience feedback
|
|
88
|
-
|
|
89
|
-
⚠️ REQUIRED WORKFLOW (do NOT skip steps):
|
|
90
|
-
1. create_job → Returns jobId (job is now QUEUED, not complete!)
|
|
91
|
-
2. wait_for_result → Checks status, waits, and retrieves results (takes 2-10 min total)
|
|
92
|
-
3. If not complete, call wait_for_result again with longer wait time
|
|
93
|
-
|
|
94
|
-
DO NOT treat job creation as completion. You MUST wait for and retrieve results.`,
|
|
95
|
-
inputSchema: {
|
|
96
|
-
type: 'object',
|
|
97
|
-
properties: {
|
|
98
|
-
url: {
|
|
99
|
-
type: 'string',
|
|
100
|
-
description: 'The URL to test (must be publicly accessible). Example: "https://myapp.com/checkout"',
|
|
101
|
-
},
|
|
102
|
-
description: {
|
|
103
|
-
type: 'string',
|
|
104
|
-
description: 'Clear instructions for the human tester. Be specific about what to test and how. Example: "Test the checkout flow: Add a product to cart, proceed to checkout, fill in shipping info, and verify the order summary shows correct totals before submitting."',
|
|
105
|
-
},
|
|
106
|
-
schema: {
|
|
107
|
-
type: 'object',
|
|
108
|
-
description: 'JSON Schema defining the structure you want extracted from the tester\'s response. Example: { "type": "object", "properties": { "checkoutWorks": { "type": "boolean" }, "totalIsCorrect": { "type": "boolean" }, "issues": { "type": "array", "items": { "type": "string" } } } }',
|
|
109
|
-
},
|
|
110
|
-
targetDurationMinutes: {
|
|
111
|
-
type: 'number',
|
|
112
|
-
description: 'Time limit in minutes for the tester to complete the test after claiming. Default: 5 minutes. Range: 1-60.',
|
|
113
|
-
},
|
|
114
|
-
allowDurationExtension: {
|
|
115
|
-
type: 'boolean',
|
|
116
|
-
description: 'Allow test duration to exceed targetDurationMinutes if the tester needs more time. Default: true.',
|
|
117
|
-
},
|
|
118
|
-
maxExtensionMinutes: {
|
|
119
|
-
type: ['number', 'boolean'],
|
|
120
|
-
description: 'Maximum additional minutes allowed for extension. Set to false for unlimited. Default: false (unlimited). Range: 1-60 if number.',
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
required: ['url', 'description', 'schema'],
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'wait_for_result',
|
|
128
|
-
description: `Check status, wait, and retrieve results for a QA job in a single call.
|
|
129
|
-
|
|
130
|
-
This tool combines status checking, waiting, and result retrieval into one convenient function:
|
|
131
|
-
- Checks status BEFORE waiting (returns immediately if already complete)
|
|
132
|
-
- Waits for the specified duration (default 30s, configurable 1-300s)
|
|
133
|
-
- Checks status AFTER waiting
|
|
134
|
-
- Returns results if complete, otherwise suggests calling again with longer wait
|
|
135
|
-
|
|
136
|
-
Jobs progress through states: pending → claimed → in_progress → completed (or failed/timeout)
|
|
137
|
-
Typical completion time: 2-10 minutes total
|
|
138
|
-
|
|
139
|
-
Use this tool repeatedly with increasing wait times until you get results:
|
|
140
|
-
- First call: wait_for_result(jobId, { waitSeconds: 30 })
|
|
141
|
-
- If not complete: wait_for_result(jobId, { waitSeconds: 45 })
|
|
142
|
-
- If still not complete: wait_for_result(jobId, { waitSeconds: 60 })
|
|
143
|
-
|
|
144
|
-
Returns immediately if job is already complete (no waiting needed).`,
|
|
145
|
-
inputSchema: {
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: {
|
|
148
|
-
jobId: {
|
|
149
|
-
type: 'string',
|
|
150
|
-
description: 'The job ID returned from create_job. Example: "550e8400-e29b-41d4-a716-446655440000"',
|
|
151
|
-
},
|
|
152
|
-
waitSeconds: {
|
|
153
|
-
type: 'number',
|
|
154
|
-
description: 'How long to wait (in seconds) before checking status again. Default: 30. Range: 1-300. Increase this on subsequent calls (30 → 45 → 60) for better efficiency.',
|
|
155
|
-
minimum: 1,
|
|
156
|
-
maximum: 300,
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
required: ['jobId'],
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
};
|
|
164
|
-
});
|
|
165
|
-
/**
|
|
166
|
-
* Handle tool calls
|
|
167
|
-
*/
|
|
168
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
169
|
-
const { name, arguments: args } = request.params;
|
|
170
|
-
if (!args) {
|
|
171
|
-
throw new Error('Missing arguments');
|
|
172
|
-
}
|
|
173
|
-
switch (name) {
|
|
174
|
-
case 'create_job':
|
|
175
|
-
try {
|
|
176
|
-
// Call RunHuman API to create job
|
|
177
|
-
const response = await fetch(`${API_URL}/api/jobs`, {
|
|
178
|
-
method: 'POST',
|
|
179
|
-
headers: {
|
|
180
|
-
'Authorization': `Bearer ${API_KEY}`,
|
|
181
|
-
'Content-Type': 'application/json'
|
|
182
|
-
},
|
|
183
|
-
body: JSON.stringify({
|
|
184
|
-
url: args.url,
|
|
185
|
-
description: args.description,
|
|
186
|
-
outputSchema: args.schema,
|
|
187
|
-
targetDurationMinutes: args.targetDurationMinutes,
|
|
188
|
-
allowDurationExtension: args.allowDurationExtension,
|
|
189
|
-
maxExtensionMinutes: args.maxExtensionMinutes,
|
|
190
|
-
})
|
|
191
|
-
});
|
|
192
|
-
if (!response.ok) {
|
|
193
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
194
|
-
return {
|
|
195
|
-
content: [{
|
|
196
|
-
type: 'text',
|
|
197
|
-
text: `❌ Failed to create job
|
|
198
|
-
|
|
199
|
-
Error: ${error.error || error.message || response.statusText}
|
|
200
|
-
Status: ${response.status}
|
|
201
|
-
|
|
202
|
-
Please check:
|
|
203
|
-
- Your RUNHUMAN_API_KEY is valid
|
|
204
|
-
- The API server is running at ${API_URL}
|
|
205
|
-
- Your API key has permission to create jobs`
|
|
206
|
-
}],
|
|
207
|
-
isError: true
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const data = await response.json();
|
|
211
|
-
const duration = args.targetDurationMinutes || 5;
|
|
212
|
-
return {
|
|
213
|
-
content: [
|
|
214
|
-
{
|
|
215
|
-
type: 'text',
|
|
216
|
-
text: `⏳ Job QUEUED (not complete yet!)
|
|
217
|
-
|
|
218
|
-
Job ID: ${data.jobId}
|
|
219
|
-
Status: ${data.status}
|
|
220
|
-
URL: ${args.url}
|
|
221
|
-
Time limit: ${duration} minutes
|
|
222
|
-
|
|
223
|
-
The test has been queued for a HUMAN tester. It is NOT complete yet.
|
|
224
|
-
|
|
225
|
-
⚠️ REQUIRED NEXT STEPS:
|
|
226
|
-
1. Call wait_for_result(jobId: "${data.jobId}", waitSeconds: 30)
|
|
227
|
-
2. If not complete, call wait_for_result again with waitSeconds: 45
|
|
228
|
-
3. Continue with increasing wait times (60, 90, etc.) until complete
|
|
229
|
-
|
|
230
|
-
Typical completion time: 2-10 minutes total.
|
|
231
|
-
|
|
232
|
-
DO NOT consider this task done until you have retrieved the results!`,
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
return {
|
|
239
|
-
content: [{
|
|
240
|
-
type: 'text',
|
|
241
|
-
text: `❌ Error creating job
|
|
242
|
-
|
|
243
|
-
${error instanceof Error ? error.message : 'Unknown error'}
|
|
244
|
-
|
|
245
|
-
Please check:
|
|
246
|
-
- The API server is running at ${API_URL}
|
|
247
|
-
- Your network connection
|
|
248
|
-
- Your .env file configuration`
|
|
249
|
-
}],
|
|
250
|
-
isError: true
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
case 'wait_for_result':
|
|
254
|
-
try {
|
|
255
|
-
const waitSeconds = Math.min(Math.max(args.waitSeconds || 30, 1), 300);
|
|
256
|
-
// Helper function to check job status
|
|
257
|
-
const checkJobStatus = async () => {
|
|
258
|
-
const response = await fetch(`${API_URL}/api/job/${args.jobId}`, {
|
|
259
|
-
headers: {
|
|
260
|
-
'Authorization': `Bearer ${API_KEY}`
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
if (!response.ok) {
|
|
264
|
-
if (response.status === 404) {
|
|
265
|
-
throw new Error(`Job not found: ${args.jobId}`);
|
|
266
|
-
}
|
|
267
|
-
throw new Error(`Failed to get job status: ${response.status} ${response.statusText}`);
|
|
268
|
-
}
|
|
269
|
-
return await response.json();
|
|
270
|
-
};
|
|
271
|
-
// Check status BEFORE waiting
|
|
272
|
-
console.error(`[wait_for_result] Checking initial status for job ${args.jobId}...`);
|
|
273
|
-
let job = await checkJobStatus();
|
|
274
|
-
// If already complete, return immediately
|
|
275
|
-
if (job.status === 'completed' || job.status === 'failed' || job.status === 'timeout') {
|
|
276
|
-
console.error(`[wait_for_result] Job already in terminal state: ${job.status}`);
|
|
277
|
-
if (job.status === 'completed') {
|
|
278
|
-
return {
|
|
279
|
-
content: [
|
|
280
|
-
{
|
|
281
|
-
type: 'text',
|
|
282
|
-
text: `✅ Test completed!
|
|
283
|
-
|
|
284
|
-
Job ID: ${job.id}
|
|
285
|
-
|
|
286
|
-
**Test Results:**`
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
type: 'text',
|
|
290
|
-
text: JSON.stringify({
|
|
291
|
-
result: job.result || {},
|
|
292
|
-
testerData: job.testerData
|
|
293
|
-
}, null, 2)
|
|
294
|
-
}
|
|
295
|
-
]
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
else if (job.status === 'failed') {
|
|
299
|
-
return {
|
|
300
|
-
content: [{
|
|
301
|
-
type: 'text',
|
|
302
|
-
text: `❌ Job failed
|
|
303
|
-
|
|
304
|
-
Job ID: ${job.id}
|
|
305
|
-
Error: ${job.error || 'Unknown error'}`
|
|
306
|
-
}],
|
|
307
|
-
isError: true
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
else { // timeout
|
|
311
|
-
return {
|
|
312
|
-
content: [{
|
|
313
|
-
type: 'text',
|
|
314
|
-
text: `⏰ Job timed out
|
|
315
|
-
|
|
316
|
-
Job ID: ${job.id}
|
|
317
|
-
|
|
318
|
-
The tester did not complete the test in time.`
|
|
319
|
-
}],
|
|
320
|
-
isError: true
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// Job not complete yet, wait and check again
|
|
325
|
-
console.error(`[wait_for_result] Job status: ${job.status}. Waiting ${waitSeconds} seconds...`);
|
|
326
|
-
const statusEmoji = {
|
|
327
|
-
pending: '⏳',
|
|
328
|
-
claimed: '👤',
|
|
329
|
-
in_progress: '🔄'
|
|
330
|
-
};
|
|
331
|
-
const emoji = statusEmoji[job.status] || '📊';
|
|
332
|
-
await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
|
|
333
|
-
// Check status AFTER waiting
|
|
334
|
-
console.error(`[wait_for_result] Checking status after ${waitSeconds}s wait...`);
|
|
335
|
-
job = await checkJobStatus();
|
|
336
|
-
// Check if complete now
|
|
337
|
-
if (job.status === 'completed') {
|
|
338
|
-
return {
|
|
339
|
-
content: [
|
|
340
|
-
{
|
|
341
|
-
type: 'text',
|
|
342
|
-
text: `✅ Test completed!
|
|
343
|
-
|
|
344
|
-
Job ID: ${job.id}
|
|
345
|
-
|
|
346
|
-
**Test Results:**`
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
type: 'text',
|
|
350
|
-
text: JSON.stringify({
|
|
351
|
-
result: job.result || {},
|
|
352
|
-
testerData: job.testerData
|
|
353
|
-
}, null, 2)
|
|
354
|
-
}
|
|
355
|
-
]
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
else if (job.status === 'failed') {
|
|
359
|
-
return {
|
|
360
|
-
content: [{
|
|
361
|
-
type: 'text',
|
|
362
|
-
text: `❌ Job failed
|
|
363
|
-
|
|
364
|
-
Job ID: ${job.id}
|
|
365
|
-
Error: ${job.error || 'Unknown error'}`
|
|
366
|
-
}],
|
|
367
|
-
isError: true
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
else if (job.status === 'timeout') {
|
|
371
|
-
return {
|
|
372
|
-
content: [{
|
|
373
|
-
type: 'text',
|
|
374
|
-
text: `⏰ Job timed out
|
|
375
|
-
|
|
376
|
-
Job ID: ${job.id}
|
|
377
|
-
|
|
378
|
-
The tester did not complete the test in time.`
|
|
379
|
-
}],
|
|
380
|
-
isError: true
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
// Still not complete, suggest calling again with longer wait
|
|
384
|
-
const nextWait = Math.min(waitSeconds + 15, 120);
|
|
385
|
-
const statusMessage = job.status === 'pending' ?
|
|
386
|
-
'Waiting for a tester to claim this job...' :
|
|
387
|
-
'The tester is working on your test...';
|
|
388
|
-
return {
|
|
389
|
-
content: [{
|
|
390
|
-
type: 'text',
|
|
391
|
-
text: `${emoji} Job Status: ${job.status}
|
|
392
|
-
|
|
393
|
-
Job ID: ${job.id}
|
|
394
|
-
|
|
395
|
-
${statusMessage}
|
|
396
|
-
|
|
397
|
-
⏰ Waited ${waitSeconds}s, job not complete yet.
|
|
398
|
-
|
|
399
|
-
💡 Suggestion: Call wait_for_result again with waitSeconds: ${nextWait}
|
|
400
|
-
Typical completion time: 2-10 minutes total.`
|
|
401
|
-
}]
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
catch (error) {
|
|
405
|
-
return {
|
|
406
|
-
content: [{
|
|
407
|
-
type: 'text',
|
|
408
|
-
text: `❌ Error waiting for result
|
|
409
|
-
|
|
410
|
-
${error instanceof Error ? error.message : 'Unknown error'}`
|
|
411
|
-
}],
|
|
412
|
-
isError: true
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
default:
|
|
416
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
417
|
-
}
|
|
41
|
+
// Create the MCP server with configuration
|
|
42
|
+
const server = createMcpServer({
|
|
43
|
+
apiUrl: API_URL,
|
|
44
|
+
apiKey: API_KEY,
|
|
418
45
|
});
|
|
419
46
|
/**
|
|
420
|
-
* Start the server
|
|
47
|
+
* Start the server with stdio transport
|
|
421
48
|
*/
|
|
422
49
|
async function main() {
|
|
423
50
|
const transport = new StdioServerTransport();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,iEAAiE;AACjE,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,gBAAgB;AAChB,2CAA2C;AAC3C,0EAA0E;AAC1E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhF,MAAM,OAAO,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,+BAA+B,CAAC;AAC7F,MAAM,OAAO,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;AAE1D,IAAI,CAAC,OAAO,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC9C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACnC,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACnC,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACzC,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAC;IACzF,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,KAAK,CAAC,oCAAoC,OAAO,EAAE,CAAC,CAAC;AAC7D,OAAO,CAAC,KAAK,CAAC,qBAAqB,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;AAElE,2CAA2C;AAC3C,MAAM,MAAM,GAAG,eAAe,CAAC;IAC7B,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,OAAO;CAChB,CAAC,CAAC;AAEH;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Library Exports
|
|
3
|
+
*
|
|
4
|
+
* This file provides library exports for programmatic use.
|
|
5
|
+
* The index.ts file is the CLI entry point (with shebang).
|
|
6
|
+
*/
|
|
7
|
+
export { createMcpServer } from './mcp-server-factory.js';
|
|
8
|
+
export type { McpServerConfig, CreateJobArgs, WaitForResultArgs, JobResponse, CreateJobResponse } from './types.js';
|
|
9
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/lib.js
ADDED
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a configured MCP Server instance without connecting a transport.
|
|
5
|
+
* This allows the server to be used with different transports (stdio, HTTP).
|
|
6
|
+
*/
|
|
7
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
+
import type { McpServerConfig } from './types.js';
|
|
9
|
+
export declare function createMcpServer(config: McpServerConfig): Server;
|
|
10
|
+
//# sourceMappingURL=mcp-server-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-factory.d.ts","sourceRoot":"","sources":["../src/mcp-server-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,OAAO,KAAK,EAAE,eAAe,EAAoC,MAAM,YAAY,CAAC;AAQpF,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAkD/D"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a configured MCP Server instance without connecting a transport.
|
|
5
|
+
* This allows the server to be used with different transports (stdio, HTTP).
|
|
6
|
+
*/
|
|
7
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { createJobToolDefinition, handleCreateJob, waitForResultToolDefinition, handleWaitForResult, } from './tools/index.js';
|
|
10
|
+
export function createMcpServer(config) {
|
|
11
|
+
const server = new Server({
|
|
12
|
+
name: 'runhuman-mcp-server',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
}, {
|
|
15
|
+
capabilities: {
|
|
16
|
+
tools: {},
|
|
17
|
+
prompts: {},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
// Register prompts
|
|
21
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
22
|
+
prompts: [
|
|
23
|
+
{
|
|
24
|
+
name: 'explain_runhuman',
|
|
25
|
+
description: 'Get an explanation of how to use RunHuman for QA testing',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}));
|
|
29
|
+
// Register tools list
|
|
30
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
31
|
+
tools: [createJobToolDefinition, waitForResultToolDefinition],
|
|
32
|
+
}));
|
|
33
|
+
// Handle tool calls
|
|
34
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
35
|
+
const { name, arguments: args } = request.params;
|
|
36
|
+
if (!args) {
|
|
37
|
+
throw new Error('Missing arguments');
|
|
38
|
+
}
|
|
39
|
+
switch (name) {
|
|
40
|
+
case 'create_job':
|
|
41
|
+
return handleCreateJob(args, config);
|
|
42
|
+
case 'wait_for_result':
|
|
43
|
+
return handleWaitForResult(args, config);
|
|
44
|
+
default:
|
|
45
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return server;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=mcp-server-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-factory.js","sourceRoot":"","sources":["../src/mcp-server-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,EAAE;SACZ;KACF,CACF,CAAC;IAEF,mBAAmB;IACnB,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,kBAAkB;gBACxB,WAAW,EAAE,0DAA0D;aACxE;SACF;KACF,CAAC,CAAC,CAAC;IAEJ,sBAAsB;IACtB,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,CAAC,uBAAuB,EAAE,2BAA2B,CAAC;KAC9D,CAAC,CAAC,CAAC;IAEJ,oBAAoB;IACpB,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QAEjD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACvC,CAAC;QAED,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,YAAY;gBACf,OAAO,eAAe,CAAC,IAAgC,EAAE,MAAM,CAAC,CAAC;YAEnE,KAAK,iBAAiB;gBACpB,OAAO,mBAAmB,CAAC,IAAoC,EAAE,MAAM,CAAC,CAAC;YAE3E;gBACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create_job Tool - Creates a QA job for human testing
|
|
3
|
+
*/
|
|
4
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import type { McpServerConfig, CreateJobArgs } from '../types.js';
|
|
6
|
+
export declare const createJobToolDefinition: Tool;
|
|
7
|
+
export declare function handleCreateJob(args: CreateJobArgs, config: McpServerConfig): Promise<{
|
|
8
|
+
content: Array<{
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}>;
|
|
12
|
+
isError?: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=create-job.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-job.tool.d.ts","sourceRoot":"","sources":["../../src/tools/create-job.tool.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAErF,eAAO,MAAM,uBAAuB,EAAE,IAiDrC,CAAC;AAEF,wBAAsB,eAAe,CACnC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAgFhF"}
|