Building MCP Tools That Hire Humans
The Model Context Protocol (MCP) is changing how AI agents interact with the world. By defining standardized tool interfaces, MCP allows AI assistants like Claude to use external services programmatically.
This tutorial shows you how to build an MCP server that enables AI agents to hire human workers through RentAHuman.What is MCP?
Model Context Protocol is an open standard for connecting AI assistants to external tools and data sources. Instead of the AI being limited to text generation, MCP lets it:
- Read and write files
- Query databases
- Call APIs
- Hire humans to do real-world tasks
Why Build a Human-Hiring MCP Tool?
AI agents are great at planning but can't execute physical tasks. By adding a human-hiring tool to your MCP server, your AI assistant can:
| AI Can Plan | Human Can Execute |
|---|
| "You need to return this package" | Actually drive to FedEx |
|---|---|
| "Someone should check on that property" | Visit and take photos |
| "These documents need signatures" | Meet client in person |
| "We need samples from 10 stores" | Visit each location |
Prerequisites
Before we start:
1. Node.js 18+ installed 2. RentAHuman API key (get one here) 3. Basic understanding of MCP (read the spec) 4. Claude Desktop or another MCP-compatible client
Step 1: Set Up the Project
mkdir mcp-rentahuman
cd mcp-rentahuman
npm init -y
npm install @modelcontextprotocol/sdk rentahuman zod
Create the basic structure:
touch index.ts
touch .env
Add your API key to .env:
RENTAHUMAN_API_KEY=your_api_key_here
Step 2: Define the Tool Schema
Create the tool definition that tells the AI what this tool does:
// index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import RentAHuman from "rentahuman";
import { z } from "zod";
// Initialize RentAHuman client
const rah = new RentAHuman({
apiKey: process.env.RENTAHUMAN_API_KEY
});
// Define the tool input schema
const HireHumanSchema = z.object({
task_title: z.string().describe("Brief title for the task"),
task_description: z.string().describe("Detailed description of what needs to be done"),
location: z.string().describe("Address or location where task should be performed"),
budget_usd: z.number().describe("Maximum budget in USD"),
deadline: z.string().optional().describe("ISO 8601 deadline, or 'asap'"),
requires_photo: z.boolean().optional().describe("Whether photo verification is needed"),
special_instructions: z.string().optional().describe("Any additional requirements")
});
type HireHumanInput = z.infer;
Step 3: Implement the Tool Handler
// Create the MCP server const server = new Server( { name: "rentahuman-mcp", version: "1.0.0" }, { capabilities: { tools: {} } } );} ] }; } catch (error) { return { content: [ { type: "text", text:// List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "hire_human", description:
Hire a human worker to complete a real-world physical task. Use this when the task requires:, inputSchema: { type: "object", properties: { task_title: { type: "string", description: "Brief title for the task (e.g., 'Pick up package from FedEx')" }, task_description: { type: "string", description: "Detailed description of what needs to be done" }, location: { type: "string", description: "Full address where task should be performed" }, budget_usd: { type: "number", description: "Maximum budget in USD (typically $15-75 for most tasks)" }, deadline: { type: "string", description: "When task must be completed by (ISO 8601 or 'asap', 'today', 'this_week')" }, requires_photo: { type: "boolean", description: "Set to true if you need photo verification of completion" }, special_instructions: { type: "string", description: "Any additional requirements or notes for the worker" } }, required: ["task_title", "task_description", "location", "budget_usd"] } }, { name: "check_task_status", description: "Check the status of a previously created task", inputSchema: { type: "object", properties: { task_id: { type: "string", description: "The task ID returned from hire_human" } }, required: ["task_id"] } } ] }; });The human will complete the task and report back with confirmation and optional photos.
- Physical presence at a location
- Human judgment or verification
- Interaction with physical objects
- Tasks that cannot be done digitally
// Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params;
if (name === "hire_human") { const input = HireHumanSchema.parse(args); try { const task = await rah.tasks.create({ title: input.task_title, description: input.task_description, location: input.location, budget: input.budget_usd, deadline: input.deadline || "asap", photoRequired: input.requires_photo || false, instructions: input.special_instructions });
return { content: [ { type: "text", text:
Task ID: ${task.id} Status: ${task.status} Budget: $${task.budget} Location: ${task.location}✅ Task created successfully!A human worker will be matched shortly. Use check_task_status with task ID "${task.id}" to monitor progress.
Estimated completion: ${task.estimatedCompletion}
❌ Failed to create task: ${error.message}} ], isError: true }; } }if (name === "check_task_status") { const taskId = (args as { task_id: string }).task_id; try { const task = await rah.tasks.get(taskId); let statusEmoji = "⏳"; if (task.status === "completed") statusEmoji = "✅"; if (task.status === "in_progress") statusEmoji = "🔄"; if (task.status === "claimed") statusEmoji = "👤";
let response =
Task: ${task.title} Worker: ${task.worker?.name || "Not yet assigned"} Location: ${task.location};${statusEmoji} Task Status: ${task.status.toUpperCase()}if (task.status === "completed") { response +=
Completed at: ${task.completedAt} Worker notes: ${task.completionNotes || "None"}; if (task.photos?.length > 0) { response +=Verification photos: ${task.photos.length} photo(s) attached; } }return { content: [{ type: "text", text: response }] }; } catch (error) { return { content: [ { type: "text", text:
❌ Failed to get task status: ${error.message}} ], isError: true }; } }return { content: [{ type: "text", text:
Unknown tool: ${name}}], isError: true }; });
Step 4: Run the Server
Add the startup code:
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("RentAHuman MCP server running");
}
main().catch(console.error);
Compile and test:
npx tsc index.ts --outDir dist --esModuleInterop
node dist/index.js
Step 5: Connect to Claude Desktop
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"rentahuman": {
"command": "node",
"args": ["/path/to/mcp-rentahuman/dist/index.js"],
"env": {
"RENTAHUMAN_API_KEY": "your_api_key_here"
}
}
}
}
Restart Claude Desktop.
Step 6: Test It!
Now you can ask Claude things like:
"I need someone to pick up my dry cleaning from 123 Main St and deliver it to my office at 456 Oak Ave. Budget is $30."
Claude will use your MCP tool to create the task:
I'll hire someone to pick up your dry cleaning.
✅ Task created successfully!
Task ID: task_abc123
Status: posted
Budget: $30
Location: 123 Main St
A human worker will be matched shortly. I'll check the status periodically and let you know when it's complete.
Advanced: Adding More Tools
Extend your MCP server with additional capabilities:
// List active tasks
{
name: "list_my_tasks",
description: "List all active and recent tasks",
// ...
}
// Cancel a task
{
name: "cancel_task",
description: "Cancel a task that hasn't been claimed yet",
// ...
}
// Rate a completed task
{
name: "rate_task",
description: "Rate the worker after task completion",
// ...
}
Security Considerations
When deploying MCP tools that can spend money:
1. Set budget limits in your RentAHuman account 2. Use task approval webhooks for high-value tasks 3. Monitor usage through the dashboard 4. Rotate API keys regularly 5. Log all tool invocations for audit trails
Full Source Code
Get the complete example:
git clone https://github.com/rentahuman/mcp-example
cd mcp-example
npm install
cp .env.example .env
Add your API key to .env
npm run build
What's Next?
You've just given your AI agent the ability to hire humans. Here are some ideas:
- Build a personal assistant that handles all your errands
- Create business automations that include human verification steps
- Develop smart home integrations that dispatch repair workers automatically
- Design research tools that gather real-world data through human observers
Resources
RentAHuman: The human execution layer for AI agents. Get Your API Key →